zoukankan      html  css  js  c++  java
  • 排序整理(c++实现),搭配图解

    十大排序算法

    img
    image
    稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面。

    不稳定:如果a原本在b的前面,而a=b,排序之后 a 可能会出现在 b 的后面。

    时间复杂度:对排序数据的总的操作次数。反映当n变化时,操作次数呈现什么规律。

    空间复杂度:是指算法在计算机内执行时所需存储空间的度量,它也是数据规模n的函数。

    元素的移动次数与关键字的初始排列次序无关的是:基数排序

    元素的比较次数与初始序列无关是:选择排序

    算法的时间复杂度与初始序列无关的是:选择排序

    1.快速排序

    方法

    假如我们要排序这几个数 6 1 2 7 9 3 4 5 10 8

    我们都是先找左边第一个数用作一开始作比较的基数就是6,然后右边开始比较,假设左边的哨兵是i,右边的是j。

    从j开始找到比6小的第一个数,停下来。在从左边的i开始找,找到第一个比6大的书停下来。最后是i停到了7,停在了5上面。

    然后对两个哨兵进行交换
    img
    img
    第一次交换结束

    然后在按照刚刚的顺序,j在向左寻找,找到4比6小,i向右寻找,找到9比6大,之后交换
    img
    第二次结束

    j在向左,找到3比6小,但是当i移动的时候两个哨兵相遇了,所以本次循环停止。停在3上面。最后在把3和基准数6交换之后完成。
    img
    以6为分界线,对左右边再次调用快速排序进行递归,算法完成
    img

    代码:

    从小到大
    //快速排序(从小到大)
    void quickSort(int left, int right, vector<int>& arr)
    {
    	if(left >= right)
    		return;
    	int i, j, base, temp;
    	i = left, j = right;
    	base = arr[left];  //取最左边的数为基准数
    	while (i < j)
    	{
    		while (arr[j] >= base && i < j)
    			j--;
    		while (arr[i] <= base && i < j)
    			i++;
    		if(i < j)
    		{
    			temp = arr[i];
    			arr[i] = arr[j];
    			arr[j] = temp;
    		}
    	}
    	//基准数归位
    	arr[left] = arr[i];
    	arr[i] = base;
    	quickSort(left, i - 1, arr);//递归左边
    	quickSort(i + 1, right, arr);//递归右边
    }
    
    从大到小
    //快速排序(从大到小)
    void quickSort(int left, int right, vector<int>& arr)
    {
    	if(left >= right) //递归边界条件
    		return;
    	if(left < 0 || right >= arr.size())
    	{
    		cout << "error args! array bound." << endl;
    		return;
    	}//非法输入判断,防止数组越界
    	int i, j, base, temp;
    	i = left, j = right;
    	base = arr[left];  //取最左边的数为基准数
    	while (i < j)
    	{
    		while (arr[j] <= base && i < j)
    			j--;
    			while (arr[i] >= base && i < j)
    			i++;
    		if(i < j)
    		{
    			temp = arr[i];
    			arr[i] = arr[j];
    			arr[j] = temp;
    		}
    	}
    	//基准数归位
    	arr[left] = arr[i];
    	arr[i] = base;
    	quickSort(left, i - 1, arr);//递归左边
    	quickSort(i + 1, right, arr);//递归右边
    }
    

    2.冒泡排序

    image

    方法

    冒泡其实很好理解

    冒泡就是把最大的东西放在后面(小到大)或把最小的放在后面(大到小)

    方法就是在一次循环中遍历找到最大的数放到后面,找到一个比前面大的数就进行交换,

    在从剩下的数中在重发刚刚的循环。完成

    代码:

    //冒泡排序
    void BubbleSort(int* h, size_t len)
    {
        if(h==NULL) return;
        if(len<=1) return;
        int temp;
        //i是次数,j是具体下标
        for(int i=0;i<len-1;++i)
        {
            for(int j=0;j<len-1-i;++j)
            {
                if(h[j]>h[j+1])
                {
                    temp = h[j];
        		h[j+1] = h[j];
        		h[j] = temp;
                }   
            }
        }           
    }
    

    3.选择排序

    image

    方法:

    选择排序和冒泡的区别就是,他是最后交换,所以比冒泡做了优化,减少了交换次数。

    方法就是用一个数做基准数,在一次循环中找到一个最大或最小的数,然后与最左或最右的数进行交换。

    每次循环用相同方法。完成

    代码

    //选择排序
    void SelectionSort(int* h, size_t len)
    {
        if(h==NULL) return;
        if(len<=1) return;
    
        int minindex,i,j,temp;
        //i是次数,也即排好的个数;j是继续排
        for(i=0;i<len-1;++i)
        {
            minindex=i;
            for(j=i+1;j<len;++j)
            {
                if(h[j]<h[minindex]) minindex=j;
            }
            temp = h[j];//最后进行交换
        	h[j+1] = h[j];
        	h[j] = temp;
        }
    }
    

    4.插入排序

    img

    方法

    我的方法就是把要排列的数组看成两个数组

    排序好的,和没有加入排序的。

    每次从没有排序好的那一部分中取出一个数,和排序好的做比较,不符合要求就把数字向后移动一位留出空间,符合要求就进行插入操作。

    void InsertSort(int a[], int len)
    {
    	int i, j, k; 
    	int tmp;
    	for (i = 1; i < len; i++)	{ 
    		k = i;			//待插入元素位置
    		tmp = a[k];	    //先拿出来
     
    		for (j = i - 1; (j >= 0) && (a[j] > tmp); j--){
    			a[j + 1] = a[j];			//只要大,则元素后移
    			k = j;						//记录移动的位置
    		} 
    		a[k] = tmp;		//元素插入 
    	} 
    }
    

    5.归并排序

    方法

    先把一个数组的所有的数字分成一半,一半,再一半,最后临时开辟的空间中排序合并。

    img
    img

    void  MergeArray(int* arr, size_t left, size_t mid, size_t right, int* temp)
    {
        if(arr==NULL) return;
    
        size_t i=left,j=mid+1,k=0;
        while(i<=mid && j<=right)
        {
            if(arr[i]<=arr[j])
            {
                temp[k++]=arr[i++];
                continue;
            }
    
            temp[k++]=arr[j++];
        }
    
        //将左边剩余元素填充进temp中
        while(i<=mid)
            temp[k++]=arr[i++];
    	//将右边子数组剩余部分填充到temp中
        while(j<=right)
            temp[k++]=arr[j++];
    	//将融合后的数据拷贝到原来的数据对应的子空间中
        memcpy(&arr[left],temp,k*sizeof(int));
    }
    //归并排序
    void MMergeSort(int* arr, size_t left, size_t right, int* temp)
    {
        if(left<right)
        {
            //分治
            size_t mid=(left+right)/2;
            MMergeSort(arr, left, mid, temp);
            MMergeSort(arr, mid+1,right, temp);
            MergeArray(arr,left, mid, right, temp);
        }
    }
    
    //void MergeSort(int* h, size_t len)
    //{
        //if(h==NULL) return;
        //if(len<=1) return;
        //int* temp=(int*)malloc(len,sizeof(int));
        //MMergeSort(h, 0, len-1, temp);
        //memcpy(h,temp,sizeof(int)*len);
        //free(temp);
    //}
    

    6.希尔排序(就是分组的插入排序)

    就是通过分组,再把分组里的书进行插入算法,这样比较的次数可能会减少。

    分组一般是总数除2

    void ShellSort(int a[], int len)
    {
    	int i, j, k, tmp;
    	int gap = len;
     
    	do{	
    		//gap的选择可以有多中方案,如gap = gap/2,这里使用的是业界统一实验平均情况最好的,收敛为1
    		gap = gap / 2;
    		for (i = gap; i < len; i += gap)  //分成len/gap组
    		{
    			//每组使用插入排序
    			k = i;
    			tmp = a[k];
    			for (j = i - gap; (j >= 0) && (a[j] > tmp); j -= gap){
    				a[j + gap] = a[j];
    				k = j;
    			}
    			a[k] = tmp; 
    		}
    	} while (gap > 1);
    }
    

    7.堆排序(用到完全二叉树)

    堆分为两类:
    1、最大堆(大顶堆):堆的每个父节点都大于其孩子节点;
    2、最小堆(小顶堆):堆的每个父节点都小于其孩子节点;
    image
    堆的存储:
    一般都用数组来表示堆,i结点的父结点下标就为(i – 1) / 2。它的左右子结点下标分别为2 * i + 1和2 * i + 2。如下图所示:
    image

    实现图解

    在这里插入图片描述

    // 从小到大排序
    void Down(int array[], int i, int n) { // 最后结果就是大顶堆
        int temp;
        int parent = i;                    // 父节点下标
        int child  = 2 * i + 1;            // 子节点下标
        while (child < n) {
            if (child + 1 < n && array[child] < array[child + 1]) { // 判断子节点那个大,大的与父节点比较
                child++;
            }
            if (array[parent] < array[child]) { // 判断父节点是否小于子节点
                temp = array[parent];
                array[parent] = array[child];
                array[child] = temp;     // 交换父节点和子节点
                parent = child;                 // 子节点下标 赋给 父节点下标
            }
            child = child * 2 + 1; // 换行,比较下面的父节点和子节点
        }
    }
    
    void BuildHeap(int array[], int size) {
        for (int i = size / 2 - 1; i >= 0; i--) { // 倒数第二排开始, 创建大顶堆,必须从下往上比较
            Down(array, i, size);                 // 否则有的不符合大顶堆定义
        }
    }
    
    void HeapSort(int array[], int size) {
        BuildHeap(array, size); // 初始化堆
        for (int i = size - 1; i > 0; i--) {
            temp = array[0];
            array[0] = array[i];
            array[i] = temp; // 交换顶点和第 i 个数据
                               // 因为只有array[0]改变,其它都符合大顶堆的定义,所以可以从上往下重新建立
            Down(array, 0, i); // 重新建立大顶堆
    
        }
    }
    

    桶排序

    其实桶排序也有插入排序的意思,他只是把插入排序变成分组的罢了。
    image

    原理

    假如有途中这么几个数要排序。我们假定要分成5个桶进行排序。先找到最大数和最小数是7,110。所以一组的区间就是(110-7)/5=28.8的大小。
    然后就是把数据插入桶中,比如第一个是7,就直接插入7属于的第0个区间。加入桶不是空的,插入数据的时候就要使用插入排序的操作。使桶里面的数据有序。
    最后在把不为空的桶连接起来。排序完成。

    #include <stdio.h>
    #include <stdlib.h>
     
    //链表结点描述  
    typedef struct Node{  
        double key;  
        struct Node * next;   
    }Node;  
    //辅助数组元素描述  
    typedef struct{  
        Node * next;  
    }Head; 
     
    void bucketSort(double* a,int n)  
    {  
    	int i,j;  
    	Head head[10]={NULL};  
    	Node * p;  
    	Node * q;  
    	Node * node;  
    	for(i=0;i<=n;i++){  
    		node=(Node*)malloc(sizeof(Node));  
    		node->key=a[i];  
    		node->next=NULL;  
    		p = q =head[(int)(a[i]*10)].next;  
    		if(p == NULL){  
    			head[(int)(a[i]*10)].next=node;  
    			continue;  
    		}  
    		while(p){  
    			if(node->key < p->key)  
    				break;  
    			q=p;  
    			p=p->next;  
    		}  
    		if(p == NULL){  
    			q->next=node;  
    		}else{  
    			node->next=p;  
    			q->next=node;  
    		}  
    	}  
    	j=0;  
    	for(i=0;i<10;i++){  
    		p=head[i].next;  
    		while(p){  
    			a[j++]=p->key;  
    			p=p->next;  
    		}  
    	}  
    }
     
    int main(int argc, char* argv[])
    {
    	int i;  
    	double a[13]={0.5,0.13,0.25,0.18,0.29,0.81,0.52,0.52,0.83,0.52,0.69,0.13,0.16};
    	bucketSort(a,12);  
    	for(i=0;i<=12;i++)  
    		printf("%-6.2f",a[i]);  
    	printf("
    ");  
    	return 0;
    }
    
    

    计数排序

    这是一种特殊的排序,加入知道要排序的数组在一个什么区间,通过计数排序,也算是投机取巧的方式。这种方式最快。

    方法

    假如排序的数载0~10之间,分别是9,3,5,4,9,1,2,7,8,1,3,6,5,3,4,0,10,9 ,7,9。
    一开始大小为11的数组,全部初始化为0
    image
    遍历数组,如果遍历到4,就在下标为4的数字+1,记录数字的个数
    image
    最后按照数组个数,直接输出排序后的数组。

    #include <stdio.h>
    #include <stdlib.h>
    #define random(x) rand()%(x)
    #define NUM 100     // 产生100个随机数
    #define MAXNUM 200     //待排序的数字范围是0-200
    void countingSort(int A[], int n, int k){
        int *c, *b;
        int i;
        c = (int *)malloc(sizeof(int)*k);/*临时数组,注意它的大小是待排序序列中值最大的那个。如假定该排序序列中最大值为1000000,则该数组需要1000000*sizeof(int)个存储单元*/
        b = (int *)malloc(sizeof(int)*n);  /*存放排序结果的数组*/
        for (i = 0; i < k; i++)
            c[i] = 0;                       /*初始化*/
        for (i = 0; i < n; i++)
            c[A[i]] += 1;                   /*统计数组A中每个值为i的元素出现的次数*/
        for (i = 1; i < k; i++)
           c[i] = c[i - 1] + c[i];         /*确定值为i的元素在数组c中出现的位置*/
        for (i = n - 1; i >= 0; i--)
        {
            b[c[A[i]] - 1] = A[i];       /*对A数组,从后向前确定每个元素所在的最终位置;*/
            c[A[i]] -= 1;
        }
        for (i = 0; i < n; i++)
            A[i] = b[i];                /*这个目的是返回A数组作为有序序列*/
        free(c);
        free(b);
    }
    void printArray(int A[], int n){
        int i = 0;
        for (i = 0; i < n; i++){
            printf("%4d", A[i]);
        }
        printf("
    ");
    }
    /*测试*/
    int main()
    {
        int A[NUM];
        int i;
        for (i = 0; i < NUM; i++)
            A[i] = random(MAXNUM);
        printf("before sorting:
    ");
        printArray(A, NUM);
        countingSort(A, NUM, MAXNUM);
        printf("after sorting:
    ");
        printArray(A, NUM);
        return 0;
    }
    

    基数排序

    原理

    基数排序其实跟计数排序差不多,只要理解了计数排序,基数排序就其实一样
    image
    首先计数排序的数组范围太大,数组就会太大,所以基数排序就是以多次的计数排序搞定排序。根据上面的图示很清楚的看到两位数经过十位和个位数的两次排序,完成排序。

    #include <iostream>
    using namespace std;
     
    /*
    * 打印数组
    */
    void printArray(int array[],int length)
    {
    	for (int i = 0; i < length; ++i)
    	{
    		cout << array[i] << " ";
    	}
    	cout << endl;
    }
    /*
    *求数据的最大位数,决定排序次数
    */
    int maxbit(int data[], int n) 
    {
        int d = 1; //保存最大的位数
        int p = 10;
        for(int i = 0; i < n; ++i)
        {
            while(data[i] >= p)
            {
                p *= 10;
                ++d;
            }
        }
        return d;
    }
    void radixsort(int data[], int n) //基数排序
    {
        int d = maxbit(data, n);
        int tmp[n];
        int count[10]; //计数器
        int i, j, k;
        int radix = 1;
        for(i = 1; i <= d; i++) //进行d次排序
        {
            for(j = 0; j < 10; j++)
                count[j] = 0; //每次分配前清空计数器
            for(j = 0; j < n; j++)
            {
                k = (data[j] / radix) % 10; //统计每个桶中的记录数
                count[k]++;
            }
            for(j = 1; j < 10; j++)
                count[j] = count[j - 1] + count[j]; //将tmp中的位置依次分配给每个桶
            for(j = n - 1; j >= 0; j--) //将所有桶中记录依次收集到tmp中
            {
                k = (data[j] / radix) % 10;
                tmp[count[k] - 1] = data[j];
                count[k]--;
            }
            for(j = 0; j < n; j++) //将临时数组的内容复制到data中
                data[j] = tmp[j];
            radix = radix * 10;
        }
    }
     
    int main()
    {
    	int array[10] = {73,22,93,43,55,14,28,65,39,81};
    	radixsort(array,10);
    	printArray(array,10);
    	return 0;
    }
    

    参考文章

    https://www.cnblogs.com/chengxiao/p/6129630.html
    https://blog.csdn.net/weixin_42109012/article/details/91668543
    https://blog.csdn.net/qq_28584889/article/details/88136498

  • 相关阅读:
    vi常用操作
    Python练习题
    Jmeter也能IP欺骗!
    mysql主从配置
    性能测试之mysql监控、优化
    Git 命令
    Chrome——F12 谷歌开发者工具详解
    Appscan
    微信群发红包抢红包设计测试用例
    MySQL基础篇(1)SQL基础
  • 原文地址:https://www.cnblogs.com/sunnylinry/p/14590393.html
Copyright © 2011-2022 走看看