排序
//主函数 #include #include int main() { int arr[] = { 9, 58, 53, 78, 61, 81,23}; int n = sizeof(arr) / sizeof(arr[0]); //bubbleSort(arr, n); //selectionSort(arr, n); //selSortSup(arr, n); //insertionSort(arr, n); //merSort(arr, n); //quickSort(arr, n); shellSort(arr, n); for (int i = 0; i < n; ++i) { printf("%d ", arr[i]); } printf("\n"); }
// 冒泡(优化) //冒泡思想:每趟将未排序的数两两比较,找到当前未排元素中最大的 //优化:加上标志位,若没到n-1趟已排序好,只需遍历一遍发现没有再做交换即为有序,则退出循环 //时间复杂度为 O(N^2) 空间复杂度为 O(1) 稳定排序 int* bubbleSort(int* A, int n) { for (int i = 0; i < n-1; i++)//n个数,最多需排 n-1趟 { bool isOrder = true; //加标志位,判断是否排好序(即为遍历一遍不再发上交换) for (int j = 0; j < n - i-1; j++) //每一趟,相邻的数两两比较,升序a[j]>a[j+1]则交换,置标志位false { if (A[j] > A[j + 1]) { int temp = A[j]; A[j] = A[j + 1]; A[j + 1] = temp; isOrder = false; } } if (isOrder == true)//有序,则跳出循环 { break; } } return A; } // 选择排序 //选择排序思想,将最小的元素放到0位置,将次小的元素放到1位置上等等 //时间复杂度为 O(N^2) 空间复杂度为 O(1) 不稳定排序 int* selectionSort(int* A, int n) { for (int i = 0; i < n - 1; i++) //选择第i小的数,依次找到最小、次小等 { for (int j = i+1 ; j < n ; j++) //拿第i个数,和第i+1个数到第n个数作比较 { if (A[i] > A[j]) //a[i]>a[j]则交换 { int temp = A[i]; A[i] = A[j]; A[j ] = temp; } } } return A; } // 选择(优化) //思想同选择排序,优化减少交换次数,遍历一遍,记录最小的值和下标,找到未排序中最小的再交换 //时间复杂度为 O(N^2) 空间复杂度为 O(1) 不稳定排序 int* selSortSup(int* A, int n) { int i, j; int index = 0; //记录下标位置 for (i = 0; i < n - 1; i++) { int min = A[i]; //先将最小值初值赋为a[i] int flag = false; //判断是否找到比a[i]更小的值,有则标志位true,无则false,不用交换 for (j = i + 1; j < n; j++) { if (A[j] < min) { min = A[j]; index = j; flag = true; } } if (flag) //标志位为true,则交换a[i]和a[index] { int temp = A[index]; A[index] = A[i]; A[i] = temp; } } return A; } // 插入排序 //思想即为从第i个元素(i从2开始)开始每个元素视为待插入元素,从第i-1个已排好的元素比较大小,如若a[j]>a[i],则a[j]后移,最终a[j+1]即为待插元素的位置 //时间复杂度为 O(N^2) 空间复杂度为 O(1) 稳定排序 int* insertionSort(int* A, int n) { for (int i = 1; i < n; i++) //a[1...len-1]依次插入,i从1开始,因为第0个元素不需要排序 { int cur = A[i]; //提前保存待插入元素 int j = i - 1; for (; j >= 0; j--) //从i-1到0寻找插入位置,从后往前查找避免元素覆盖的问题 { if (A[j] > cur) { A[j + 1] = A[j]; } else { break; } } A[++j] = cur; //a[j+1]即为待插元素的位置 } return A; } // 归并排序 //思想是二路分治,将大规模的排序问题分成两个子问题,两个子问题再分成两个子问题,直到只剩一个 元素时不需要比较,再进行依次合并,最终使数字有序 //时间复杂度为 O(NlogN) 空间复杂度为 O(N) 稳定排序 void merger_sort(int arr[],int start,int end); void merger(int arr[],int start,int mid,int end); void mer_sort(int arr[],int n) //为了与前面的排序输入保持输入一致性 { merger_sort(arr,0,n-1); } void merger_sort(int arr[],int start,int end) { if (start < end) { int mid = (start + end)/2; merger_sort(arr,start,mid); //分成两个子问题,递归解决 merger_sort(arr,mid+1,end); //分成两个子问题,递归解决 merger(arr,start,mid,end); //合并 } } void merger(int arr[],int start,int mid,int end) { int *brr = (int *)malloc(sizeof(int) * (end - start + 1)); //开辟一个新空间,临时存放合并的元素 int low1 = start; //左半部分的左下标 int high1 = mid; //左半部分的右下标 int low2 = mid+1; //右半部分的左下标 int high2 = end; //右半部分的右下标 int i = 0; while (low1 <= high1 && low2 <= high2) //当两个部分都还有元素的时候 { if (arr[low1] < arr[low2]) //两部分最左边的元素开始比较,小的则放到brr数组里 { brr[i++] = arr[low1++]; } else { brr[i++] = arr[low2++]; } } while (low1 <= high1) //当右半部分元素已完,则将左半部分元素依次放到brr数组里 { brr[i++] = arr[low1++]; } while (low2 <= high2) //当左半部分元素已完,则将右半部分元素依次放到brr数组里 { brr[i++] = arr[low2++]; } int k = 0; //brr下标 for (int j=start;j<=end;++j) //将brr临时存放的数据重新放到arr里 { arr[j] = brr[k++]; } } // 快排 //快排的思想是以一个key为基准,将待排数分为两部分,分别为大于key的和小于key的,然后每部分进行递归,当每部分只有一个数时,排序结束 //时间复杂度为 O(NlogN) 空间复杂度为 O(NlogN) ~ O(N),与选择的划分值有关 不稳定排序 快排的常量系数较低,并不是比堆排序和归并排序优良,所以才叫快速排序 void quiSort(int* A, int l, int r); int partion(int* A, int l, int r); int* quickSort(int* A, int n) //为了与前面的排序输入保持输入一致性 { int l = 0; int r = n - 1; quiSort(A, l, r); return A; } void quiSort(int* A, int l,int r) //递归解决子问题 { int mid; if(l < r) { mid = partion(A,l,r); quiSort(A,l,mid-1); quiSort(A, mid+1, r); } } int partion(int* A,int l, int r) //划分过程 { int key = A[l]; //以第一个数为基准,也可以优化 while (l < r) { while (A[r] >= key && l < r) { r--; } A[l] = A[r]; //将右边小的放到左边 while(A[l] <= key && l < r) { l++; } A[r] = A[l]; //将左边大的放到右边 } A[l] = key; //将最终的key值放到合适的位置 return l; //将key值返回,继续递归划分其他两个部分 } // 希尔排序 //思想是插入排序改良的排序算法,步长大小的选择是希尔排序的关键,当插入排序基本有序时,是用直接排序较快,因此先让待排序数基本有序是希尔排序的想法 //时间复杂度为 O(NlogN) 空间复杂度为 O(1) 不稳定排序 int* shellSort(int* A, int n) { assert(A != NULL && n >= 0); int i, j, gap; for (gap = n / 2; gap>0; gap /= 2) // 步长序列为n/2,n/4....直到1,排序完成 { for (i = gap; i < n; i++) //相当于将步长为1的插入排序改成步长为gap { for (j = i - gap; j >= 0; j -= gap) // { if (A[j + gap] < A[j]) { int tmp = A[j + gap]; A[j + gap] = A[j]; A[j] = tmp; } } } } return A; } //堆排序 //思想是若升序则建立最大堆,交换arr[0]和arr[end],除去arr[end]再继续调整使成为最大堆,最终直至有序 //时间复杂度为 O(NlogN) 空间复杂度为 O(1) 不稳定排序 void heap_adjust(int arr[],int start,int end); void heap_sort(int arr[],int len) { for (int i=len/2-1;i>=0;--i) //从最后一个节点的父节点开始调整 { heap_adjust(arr,i,len-1); } int end = len-1;//最后一个元素的下标 while(end > 0) { int tmp = arr[0]; //交换arr[0]和arr[end] arr[0] = arr[end]; arr[end] = tmp; heap_adjust(arr,0,--end); //剩余元素堆调整 } } void heap_adjust(int arr[],int start,int end) { int temp = arr[start]; for(int i=2*start+1;i<=end;i=2*start+1) //左孩子下标为2*start+1,右孩子下标为2*start+2 { if (2*start+2<=end && arr[2*start+1] < arr[2*start+2]) { i++; //i下标里面保存的是左右孩子中最大值 } if(arr[i] > temp) { arr[start] = arr[i]; start = i; //start继续往下走 } else { break; } } arr[start] = temp; //将保存的父节点的初始值赋给此时的节点值 }
- 时间复杂度为O(n)的算法,不是基于比较的排序算法,思想来自于桶排序,主要包括基数排序和基数排序,空间复杂度为O(n)
//计数排序 //思想是对于给定的输入序列中的每一个元素x,确定该序列中值小于x的元素的个数(此处并非比较各元素的大小,而是通过对元素值的计数和计数值的累加来确定),如若有相同元素,则算小于等于元素X的个数,从后往前遍历,遍历一次-1,保持排序的稳定性 //时间复杂度为O(n) 稳定排序 通常适用于范围较小的整数
伪代码如上
//基数排序 //思想是将个位进行排序,次序不变,再将十位进行排序,直到所有位排序完成,如果位数个数不等,可再前补0达成相同位数 //时间复杂度为O(n) 稳定排序 通常适用于位数相同的整数
//桶排序 //思想是、把区间[0,1)划分成n个相同大小的子区间,或称桶,然后将n个输入数分布到各个桶中去。桶间用插入排序,然后按次序把各桶中的元素列出来即可。 //时间复杂度为O(n) 稳定排序 通常适用于均匀分布的小数
补充说明:
概念:稳定性:指相同元素的值在排序前后的相对次序不发生变化 内排序:指在排序期间数据对象全部存放在内存的排序。 外排序:指在排序期间全部对象个数太多,不能同时存放在内存,必须根据排序过程的要求,不断在内、外存之间移动的排序。比如常见的有外归并排序。 原地排序:是指不申请多余的空间来进行的排序,就是在原来的排序数据中比较和交换的排序
- 工程上的排序是综合排序;数据较小时选择插入排序;数据较大时选择快排或者其他O(NlogN)的排序
- 冒泡排序和选择排序与数组原始序列无关,时间复杂度都是O(N^2);快排和归并排序与数组原始序列也无关,时间复杂度为 O(NlogN)
- 面试题:
- 已知一个几乎有序的数组,几乎有序是指,如果把数组排好序的话,每个元素移动的距离不超过k,并且k相对于数组长度来说很小。请问选择什么方法对其排序较好?
思路:冒泡排序、选择排序、快排和归并排序与数组原始序列无关,计数和基数排序对数据特征有要求,不具有普遍性,插入排序O(N*K),改良的堆排序O(NlogK) - 判断数组中是否有重复值。必须保证额外空间复杂度为O(1)
思路:如果不限制空间复杂度可以用哈希表,限制空间复杂度的话需要排序 - 把两个有序数组合并为一个数组,第一个数组空间正好可以容乃两个数组的元素
思路:从后往前比较,避免覆盖
- 总结
原地排序 ▪ 希尔排序 ▪ 冒泡排序 ▪ 插入排序 ▪ 选择排序 ▪ 堆排序
稳定排序 ▪ 冒泡排序 ▪ 插入排序 ▪ 归并排序 ▪ 计数排序 ▪基数排序
不稳定排序 ▪ 堆排序 ▪ 快速排序 ▪ 希尔排序 ▪ 选择排序