各种排序总结

非线性时间比较类

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)

posted @ 2020-03-11 14:40  阳离子  阅读(285)  评论(0编辑  收藏  举报