排序算法
常见的排序算法
-
内部排序
- 插入查找
- 希尔排序
- 选择排序
- 冒泡排序
- 归并排序
- 快速排序
-
外部排序
1. 插入排序
思路
插入已经排好序的前面部分, 相当于在前面排好序的部分进行search.
复杂度分析:
- 最好情况: O(n)
- 最差情况: O( n 2 n^2 n2)
- 平均情况: O( n 2 n^2 n2)
代码:
void insertSort(int *arr,int num){
int tmp;
for(int i = 1; i < num; i++) {
tmp = arr[i]; //i 为未排序的位置
for(int j = i - 1; j >= 0 ;j--) {
//j 为未排序i前的位置
if(tmp < arr[j]) {
arr[j+1] = arr[j];
if( j == 0) arr[j] = tmp;
} else{
arr[j+1] = tmp;
break;
}
}
}
}
2. 希尔排序
思路
在插入排序只中, 逆序的个数 决定了交换的次数, 平均逆序的数量C(n, 2) = O( n 2 n^2 n2)
所以, 就是要降低逆序的次数, 通过分组的方法.
分组的方式不是简单的逐段分割, 而是像个某个增量的记录组成一个分组.
复杂度分析:
- 最好情况:
- 最差情况: O( n l o g n nlogn nlogn)~O( n 2 n^{2} n2)
- 平均情况: O( n l o g n nlogn nlogn)~O( n 2 n^{2} n2)
代码:
void shellSort(int *arr,int num, int *add, int addSize) {
int tmp;
int addtion; // 记录当前增量如[5,3,1]
for(int i = 0; i < addSize; i++ ) {
addtion = add[i];
for(int k = addtion ; k < num;k++) {
tmp = arr[k]; //k 为未排序的位置
for(int q = k - addtion ; q >= 0; q = q - addtion) {
//q为未排序k前的位置
if(arr[q] > tmp) {
arr[q+addtion] = arr[q];
if((q-addtion) < 0) arr[q] = tmp; // 判断是否是每组第一个数
} else{
arr[q+addtion] = tmp;
break;
}
}
}
}
}
3. 选择排序
思路
在未排序的无序子序列中找到最小或最大的
复杂度分析:
- 时间复杂度: O( n 2 n^{2} n2)
代码:
void selectSort(int * arr, int num) {
int currentMinIndex, tmp;
for(int i = 0; i < num - 1; i++) {
currentMinIndex = i;
for(int j = i+1; j < num; j++) {
if(arr[j] < arr[currentMinIndex]) currentMinIndex = j;
}
if(currentMinIndex != i) {
tmp = arr[i];
arr[i]= arr[currentMinIndex];
arr[currentMinIndex] = tmp;
}
}
}
4. 冒泡排序
思路
相邻的两个元素比较, 可以把当前无序的元素中最大最小的一直向后移.
优化:
- 当一趟中不存在交换时, 排序可以结束,利用一个flag变量可以实现.
- 每趟可以确定的有序子序列的位置为最后一次交换的位置. 利用一个变量记录最后一次交换的下标,使得带冒泡的位置为这个下标.
复杂度分析:
- 时间复杂度: O( n 2 n^{2} n2),
- 最好情况: 正序, 比较一趟即可,n-1次, 移动0次.
- 最差情况: 逆序, 比较次数 ∑ i = 1 n − 1 ( n − 1 ) = 1 2 ( n 2 − n ) \sum^{n-1}_{i=1}(n-1)=\frac{1}{2}(n^2-n) ∑i=1n−1(n−1)=21(n2−n), 移动次数 3 2 ( n 2 − n ) \frac{3}{2}(n^2-n) 23(n2−n)
代码:
void bubbleSort(int *arr,int num) {
int tmp;
int flag = 1; //记录一趟中是否有交换
int lastChangeIndex = 0;
int j,m = num - 1;
while(m>0 && flag == 1){ //上一趟不存在交换时, 已经是有序的了
flag = 0;
for(j = 0; j < m; j++ ) {
if(arr[j] > arr[j+1]){
flag = 1;
tmp = arr[j+1];
arr[j+1] = arr[j];
arr[j] = tmp;
lastChangeIndex = j;
}
}
m = lastChangeIndex; //经过一趟后, 有序子序列的前一个位置,等待冒泡
//cout << m << endl;
}
}
5. 归并排序
思路
两个有序的线性表合并过程
复杂度分析:
- 时间复杂度: O( n l o g n nlogn nlogn) , 每一趟归并时间复杂度为O(n), 需要logn躺归并.
代码:
void merge(int *arr,int num, int left1,int right1,int left2,int right2) {
int arrTmp[num];
int k =0;
int l1 = left1,l2 = left2;
while(l1<=right1 && l2 <=right2) {
if(arr[l1]>arr[l2]) {
arrTmp[k++] = arr[l2++];
}else{
arrTmp[k++] = arr[l1++];
}
}
while(l1 <= right1){
arrTmp[k++] = arr[l1++];
}
while(l2 <= right2){
arrTmp[k++] = arr[l2++];
}
for(int i = 0; i < k;i++) {
arr[left1+i]= arrTmp[i]; //copy back to orignal table
}
}
void helper(int *arr, int l,int r ){
int mid;
if(l < r) {
mid = (l + r) / 2;
helper(arr,l,mid);
helper(arr,mid+1,r);
merge(arr,r-l+1, l,mid,mid+1,r);
}
}
void mergeSort(int *arr,int num) {
helper(arr,0,num-1);
}
6. 快速排序
思路
- 分制递归的思想
- 取一个pivot值, 比它小为一个子序列, 比它大的为另一个子序列, 对每个子序列进行相同的操作.
- pivot的取值很重要:
- 如果取第一个pivot = arr[left] , 当正序或者逆序的时候, 复杂度为O( n 2 n^{2} n2)
- pivot 取随机数, 但是随机数生成要花费时间.
- pivot= median(left, center, right);
复杂度分析:
- 时间复杂度: O( n l o g n nlogn nlogn ),
- 最好情况: O( n l o g n nlogn nlogn),每次取到中间值作为pivot.
- 最差情况: O( n 2 n^{2} n2),每次取到极值做为pivot.
代码:
void sorter(int *arr,int left,int right) {
int i = left,j = right, tmp;
int pivot = arr[i];
if(right <= left) return;
while(i < j) {
cout << i <<" "<< j<< endl;
while(arr[j] >= pivot && i < j) j--; //j后面的值大于pivot
if(i < j){
tmp = arr[j];
arr[j]=arr[i];
arr[i]= tmp;
i++;
}
while(arr[i] <= pivot && i < j) i++; //i前面的值小于pivot
if(i < j){
tmp = arr[j];
arr[j]=arr[i];
arr[i]= tmp;
j--;
}
}
sorter(arr,left,j-1); //j是pivot的下标
sorter(arr,j+1,right);
}
void quickSort(int *arr,int num){
sorter(arr,0,num-1);
}