zoukankan      html  css  js  c++  java
  • 各种排序总结

    非线性时间比较类

    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)

  • 相关阅读:
    【NOIP 2003】 加分二叉树
    【POJ 1655】 Balancing Act
    【HDU 3613】Best Reward
    【POJ 3461】 Oulipo
    【POJ 2752】 Seek the Name, Seek the Fame
    【POJ 1961】 Period
    【POJ 2406】 Power Strings
    BZOJ3028 食物(生成函数)
    BZOJ5372 PKUSC2018神仙的游戏(NTT)
    BZOJ4836 二元运算(分治FFT)
  • 原文地址:https://www.cnblogs.com/woxiaosade/p/12462312.html
Copyright © 2011-2022 走看看