• 各种排序总结


    非线性时间比较类

    1. 交换排序

    1.冒泡排序

    思想:从前往后扫描,如果相邻两个元素的大小不满足要求,则进行交换。因此,每一轮可以将最大的元素放到最后一位,下一轮扫描时,就无需进行到最后一位了。

    时间复杂度:进行两重循环,因此是O(n^2)

    空间复杂度:原地排序,无需其他额外的空间,因此是O(1)

    void bubbleSort(int array[], int length) {
            int t = 0;
            for (int i = 0; i < length - 1; i++){
                for (int j = 0; j < length - 1 - i; j++){
                    if (array[j] > array[j + 1]) {
    					swap(array[i], array[j + 1]);
                }
            }
       }
    }
    

    2.快速排序

    思想:快排的一个重要思想是递归。每一轮递归,需要选出一个基准元素(最好随机选取,下面附的代码是以第一个元素为基准元素),将大于此基准元素的放到其右侧;小于他的放到其左侧。然后左侧和右侧分别进行下一轮递归。

    时间复杂度:由于快排能指数级的降低复杂度,因此是O(nlogn)

    空间复杂度:O(logn)

    void quickSort(int a[], int l, int r){
        if(l >= r) return;
        int i = l - 1, j = r + 1, x = a[l];
        while(i < j){
            do i++; while(a[i] < x);
            do j--; while(a[j] > x);
            if(i < j){
                int tmp = a[i];
                a[i] = a[j];
                a[j] = tmp;
            }
        }
        quickSort(a, l, j);
        quickSort(a, j + 1, r);
    }
    

    2. 插入排序

    1.简单插入排序

    思想:维护前x个元素为有序的,当第x+1个元素要插入时,将其插入到合适的位置即可。

    时间复杂度:O(n^2)

    空间复杂度:O(1)

    void insertSort(int array[], int length) {
            int current;
            for (int i = 0; i < length - 1; i++) {
                current = array[i + 1];
                int preIndex = i;
                while (preIndex >= 0 && current < array[preIndex]) {
                    array[preIndex + 1] = array[preIndex];
                    preIndex--;
            	}
                array[preIndex + 1] = current;
         	}
    }
    
    

    2.希尔排序

    思想:首先把整个数组,分成若干个小组,然后对每个小组进行插入排序,由于小组的数据量小,因此插入起来效率较高。小组是通过增量来形成的。较简单的增量序列是[Lenght/2, Lenth/4 .....],也有个别情景指定给出增量序列的。对于增量为Length/2时,小组为[0, length /2]、[1, length /2 + 1]......

    时间复杂度:O(nlogn)

    空间复杂度:O(1)

    void sheelSort(int array[], int length){
        for(int gap = length / 2; gap > 0; gap >>= 1){
            for(int i = gap; i < lengh; i++){
                //将array[i]插入到此小组内合适的位置
                insertSort(array, gap, i);
            }
        }
    }
    //改写插入排序
    void insertSort(int array[], int gap, int i){
        int value = array[i];
        int j = 0;
        for(j = i - gap; j >= 0 && value < array[i]; j -= gap){
            array[j + gap] = array[j];
        }
        arr[j + gap] = insert;
    }
    

    3. 选择排序

    1.选择排序

    思想:每一轮遍历,把最小的元素放到最前面。

    时间复杂度:O(n^2)

    空间复杂度:O(1)

    void selectSort(int array[], int length) {
        for (int i = 0; i < length; i++) {
            int index = i;
            for (int j = i; j < length; j++) {
                if (array[j] < array[index]) 
                    index = j; 
            }
            int temp = array[index];
            array[index] = array[i];
            array[i] = temp;
        }
    }
    

    2.堆排序

    思想:是利用堆这种数据结构来进行排序的算法。例如小顶堆,我们可以得到堆顶的最小值(根节点),将其与堆的最后一个元素交换,此时前n-1个元素可能不满足小顶堆的特性,因此让剩下n-1个元素重建一个小顶堆,则得到n个元素中的次小值。如此反复即可。

    大顶堆:父亲节点的值大于其左右儿子的值。

    小顶堆:父亲节点的值小于其左右儿子的值。

    时间复杂度:O(nlogn)

    空间复杂度:O(1)

    void heapSort(int array[], int length){
        // 1. 将整个序列构建成堆
        // length / 2 - 1 是最后一个非叶子节点,只有非叶子节点才有调整的意义
        for(int i = length / 2 - 1; i >= 0; i--){
            heapAdjust(array, i, length);
        }
        for(int i = length - 1; i > 0; i--){
            // swap
            swap(array, 0, i);
            // adjust
            heapAdjust(array, 0, i);
        }
    }
    
    void heapAdjust(int array[], int i, int length){
        // 取出顶部元素
        int value = array[i];
        // 从左儿子开始
        for(int k = i * 2 + 1; k < length; k = k * 2 + 1){
            // 如果左儿子小,那么右儿子处更容易出现“儿子大于爹”的情况
            if(k + 1 < length && array[k] < array[k + 1]){
                // 切换至右儿子
                k++;
            }
            if(array[k] > value){
                array[i] = array[k];
                i = k;
            }
            else{
                break;
            }
        }
        // i 就是最后合适的位置
        array[i] = value;
    }
    

    4. 归并排序

    1.二路归并排序

    思想:将两个有顺序的数组合并成一个有序数组。

    时间复杂度:O(nlogn)

    空间复杂度:O(n)

    void merge(int* a, int left, int right) {
    	int* ta = new int[right - left + 1];
    	int mid = (left + right) >> 1;
    	int i = left, j = mid + 1;
    	int pos = 0;
    	// merge
    	while (i <= mid || j <= right) {
    		if (i <= mid && j <= right)
    			ta[pos++] = a[i] > a[j] ? a[i++] : a[j++];
    		else if (i <= mid)
    			ta[pos++] = a[i++];
    		else
    			ta[pos++] = a[j++];
    	}
    	// copy
    	for (int i = left; i <= right; i++) {
    		a[i] = ta[i - left];
    	}
    	delete(ta);
    }
    void mergeSort(int* a, int left, int right) {
    	if (left < right) {
    		int mid = (left + right) >> 1;
    		mergeSort(a, left, mid);
    		mergeSort(a, mid + 1, right);
    		merge(a, left, right);
    	}
    }
    

    线性时间非比较类

    1.计数排序

    思想:计数排序的思想比较直观,从前到后,扫描下原序列,记录每个值下元素的个数,然后从最小值开始输出,如果此值对应的个数为2,那么输出两次就好。

    如果数据的最小值,不是从0开始的,那么这个地方,在统计该值的个数时,可以减去最小值,整体的把数据平移一下。

    在其他的排序场景下,例如统计学生的成绩,如果条件是成绩相同,我们需要保留原有数据的顺序,那么普通的计数排序无法保证保留原有顺序,他会打乱相同值的顺序(不稳定)。因此可以将其改进一下。

    ①用countArray数组统计每个值的个数

    ②countArray数组记录自己的前缀和,例如countArray数组为0、1、2、0、4,那么统计前缀和后就为0、1、3、3、7

    ③从后向前扫描原序列,通过其值v在value数组中找到对应的位置pos。位置pos=countArray[v]-1。在获得该位置后,countArray[v]--(这个地方很巧妙)。

    计数排序不太适合数据较为离散的情况,以及小数的排序。

    时间复杂度:O(n + m) m是最大值最小值之差

    空间复杂度:O(m)

    // 改进前的计数排序
    void countSort(int* array, int length) {
        // 1 得到数列的最大值、最小值
        int maxt = array[0], mint = array[0];
        for (int i = 1; i < length; i++) {
            maxt = max(maxt, array[i]);
            mint = min(mint, array[i]);
        }
        // 2 countArray数组存储每个值的个数
        int *countArray = new int[maxt - mint + 1];
        // 3 遍历数列,获得每个值的个数
        for(int i = 0; i < length; i++)
            countArray[array[i] - mint]++;
        // 4 遍历统计数组,输出结果
        int index = 0;
        for (int i = 0; i < (maxt - mint + 1); i++) {
            for (int j = 0; j < countArray[i]; j++) {
                array[index++] = i + mint;
            }
        }
    }
    

    2.基数排序

    思想:①分配根据关键字,把数组元素从前往后,分别装到相应的桶中,②收集,再从桶中收集回来。就以简单的数字排序为例,如果是个位、十位、百位的顺序,那就是最低位优先;与之相反,是最高位优先

    时间复杂度:O(N*K) K是关键字的个数

    空间复杂度:O(n + m)

    3.桶排序

    思想:计数排序和基数排序中,都用到了桶排序的思想。桶排序是先根据数据中的最大值和最小值将数据划分区间,然后每个区间对应一个桶,每个桶中的数据可以通过归并排序、快排等方法再次进行排序。但是当数据的分布极其不均匀时,他的时间复杂度退化较厉害,用的较少。

    时间复杂度:O(n + k)

    空间复杂度:O(n + k)

  • 相关阅读:
    VC++中使用ADO方式操作ACCESS数据库
    运维工程师必会的109个Linux命令
    linux上安装配置samba服务器
    ubuntu如何实现访问实际网络中windows共享文件夹
    R语言 入门知识--常用操作和例子
    坚持你选择的路
    scala eclipse plugin 插件安装
    Linux安装卸载Mysql数据库
    Hadoop HA高可用性架构和演进分析(转)
    Spring 系列: Spring 框架简介 -7个部分
  • 原文地址:https://www.cnblogs.com/woxiaosade/p/12462312.html
Copyright © 2020-2023  润新知