排序算法
- 我们想要进行某些操作,例如找到班级里某一科目成绩最高的几位学生时,我们一定是先要对成绩进行一个从高到低的排序,然后才能直观的看出成绩的排名。
- 在计算机中,最朴素的排序算法应该是冒泡排序,也即暴力排序,每次将相邻的两个元素比较&交换,时间复杂度约为 O ( n 2 ) O(n^2) O(n2),如果要排序的数据量非常大,如 1 0 6 10^6 106级甚至以上,用冒泡排序的效率必然是十分低下。那么,有没有高效的算法进行排序呢?
快速排序
-
快速排序,时间复杂度约为 O ( n l o g n ) O(nlogn) O(nlogn),是一种相对高效的算法。
-
原理:每次排序之前找到一个基准值,然后以该基准值为基础,将大于基准值的值往后放,小于基准值的值往前放。最后将基准值放到最终位置,再以基准值为分界线,左右部分进行相应的操作。直到全部排完序为止。(为了简单起见,我们一般都是取最左边的元素为基准值)。
-
注意:排序时,前后两个指针,要先从后往前开始,否则会有部分元素排序失败。
-
核心代码如下:
int QPass(int elem[], int low, int high) { int l = low, r= high; int standard = elem[low]; while(l < r) { //顺序不可更改 while(l < r && elem[r] >= standard) r--; //从右往左找第一个小于基准值standard的value if(l < r) elem[l++] = elem[r]; while(l < r && elem[l] <= standard) l++; //从左往右找第一个小于基准值standard的value if(l < r) elem[r--] = elem[l]; } elem[l] = standard; return l;//mid } void Qsort(int elem[], int low, int high) { //不要忘了加终止条件,否则会死循环 if(low > high) return; int mid; mid = QPass(elem, low, high); Qsort(elem, low, mid-1); Qsort(elem, mid+1, high); }
-
给定你一个长度为 n n n的整数数列。请你使用快速排序对这个数列按照从小到大进行排序。
输入格式:输入共两行,第一行包含整数n。
第二行包含n个整数(所有整数均在 1 ∼ 1 0 9 1∼10^9 1∼109范围内),表示整个数列。输出格式:
输出共一行,包含n个整数,表示排好序的数列。
并将排好序的数列按顺序输出。数据范围: 1 < = n < = 1 e 6 1 <= n <=1e^6 1<=n<=1e6
-
代码如下:
#include<iostream> using namespace std; const int N = 1e6+10; int n; int q[N]; void quick_sort(int q[], int l, int r) { if (l >= r) return; int x = q[l + r >> 1], i = l - 1, j = r + 1; //以中间作为基准点。若以左右作为基准点超时 while (i < j) { do i ++ ; while (q[i] < x); do j -- ; while (q[j] > x); if (i < j) swap(q[i], q[j]); } quick_sort(q, l, j); quick_sort(q, j + 1, r); } int main() { scanf("%d", &n); for(int i = 0; i < n; i++) scanf("%d", &q[i]); quick_sort(q,0,n-1); for(int i = 0; i < n; i++) printf("%d ", q[i]); return 0; }
归并排序
-
相较于快速排序,归并排序的一大特点是其具有稳定性。时间复杂度定为 n l o g ( n ) nlog(n) nlog(n)
- 其思想是:将数组进行二分,然后对子数组排好序之后,在依次合并为大数组。具体如本例,存在数组
[12,1,32,6,7,5]
,利用归并思想将其排序:
- 其思想是:将数组进行二分,然后对子数组排好序之后,在依次合并为大数组。具体如本例,存在数组
-
模板代码如下:
#include<iostream> using namespace std; const int N = 1e5+10; int q[N], tmp[N];//tmp为临时数组 void merge_sort(int q[], int l, int r) { if(l >= r) return; //表示二分到底了 int mid = l + (r-l)/2; //求二分中点 //递归求左右子数组(以mid划分) merge_sort(q, l, mid); merge_sort(q, mid+1, r); //合并操作 int i = l, j = mid + 1; int k = 0; //开始将两个子数组合并为一个子数组 while(i <= mid && j <= r) { if(q[i] <= q[j]) tmp[k++] = q[i++]; //小的元素先放入临时数组tmp else tmp[k++] = q[j++]; } //合并完后发现,一定是其中一个数组先合并完成,另一个数组仍可能有元素,且归并自底向上完成,一定是有序的。 while(i <= mid) tmp[k++] = q[i++]; while(i <= mid) tmp[k++] = q[j++]; //将值放回q数组。 for(int i = l, j = 0; i <= r; i++, j++) q[i] = tmp[j]; }
-
题目:给定你一个长度为 n的整数数列。请你使用归并排序对这个数列按照从小到大进行排序。并将排好序的数列按顺序输出。
输入输出与上面快速排序一致,范围一致。 -
代码如下:
#include<iostream> using namespace std; const int N = 1e6+10; int q[N], tmp[N]; void merge_sort(int q[], int l, int r) { if(l >= r) return; int mid = l + (r - l) / 2; merge_sort(q, l, mid); merge_sort(q, mid+1, r); int i = l, j = mid + 1; int k = 0; //tmp while(i <= mid && j <= r) { if(q[i] <= q[j]) tmp[k++] = q[i++]; else tmp[k++] = q[j++]; } while(i <= mid) tmp[k++] = q[i++]; while(j <= r) tmp[k++] = q[j++]; for(int i = l, j = 0; i <= r; i++, j++) q[i] = tmp[j]; } int main() { ios::sync_with_stdio(0); cin.tie(0);cout.tie(0); int n; cin>>n; for(int i = 0 ;i < n; i++) cin>>q[i]; merge_sort(q, 0, n - 1); for(int i = 0 ; i < n; i++) cout<<q[i]<<" "; return 0; }
-
拓展应用:求数组中的逆序对数量(逆序对定义参考线性代数)
void merge_sort(int a[], int l, int r, long long &count) //数据量很大,最大接近0.5e5!个元素 { if( l >= r) return; int mid = (l+r)>>1; merge_sort(a, l, mid, count); merge_sort(a, mid + 1, r, count); int k = 0; int i = l, j = mid + 1;//mid+1右半边起始边界 while(i <= mid && j <= r) { if(a[i] <= a[j]) b[k++] = a[i++]; else { count += (mid - i + 1 ); //即:假设前半部分和后半部分已排好序,前半部分a[i]元素大于后半部分a[j]元素, //则a[i]及其后面的mid-i个都有:大于a[j].所以共mid-i+1个逆序对 b[k++] = a[j++]; } } while(i<=mid) b[k++] = a[i++]; while(j<=r) b[k++] = a[j++]; for(int x = l, y = 0; x <= r; x++,y++) a[x] = b[y]; }