一、内部排序(关注时空复杂度、稳定性)
1.插入排序
(1)直接插入排序
算法思想:将待排序的元素插入前面已经排好的序列中。
空间复杂度:O(1) 辅助空间(i, j, temp)为常数级
时间复杂度:最好O(n) 最坏O() 平均O()
稳定性: 稳定
// 直接插入排序 n为数组长度 (不带哨兵)
void InsertSort(int A[], int n){
int i, j, temp;
for(i = 1; i < n; i++){
if(A[i] < A[i-1]){
temp = A[i];
for(j = i - 1; j >= 0 && temp < A[j]; --j)
A[j+1] = A[j];
A[j+1] = temp;
}
}
}
(2)折半插入排序
算法思想:根据待排序的元素,对前面已经排好的序列作折半查找,最后在low所指的位置插入元素,【low, i-1】或 【high, i-1】的元素都往后移。最后在low或high+1处插入元素。
时间复杂度:平均 O()
稳定性:稳定(当待排元素和mid所指元素相等时,依旧让low = mid + 1)
// 折半插入排序(带哨兵)
void BinaryInsertSort(int A[], int n){
int i, j, low, high, mid;
for(i = 2; i <= n; i++){
A[0] = A[i];
low = 1; high = i - 1;
while(low<=high){
mid = (low + high)/2;
if(A[0] >= A[mid])
low = mid + 1;
else high = mid - 1;
}
for(j = i-1; j >= low; --j){
A[j+1] = A[j];
A[low] = A[0];
}
}
2.希尔排序(先部分有序,后全局有序)
算法思想:设置步长,对距离相同步长的元素进行直接插入排序,之后调整步长,直至为1。
空间复杂度:O(1)
时间复杂度:最坏O() ; 当n在某个范围内,时间复杂度可达O()
稳定性:不稳定
适用性:仅用于顺序表
// 希尔排序(带哨兵)
void ShellSort(int A[], int n){
int i, j, d;
for(d = n/2; d >= 1; d = d/2){
for(i = d+1; i <= n; ++i){
if(A[i] < A[i-d]) {
A[0] = A[i];
for(j = i - d; j > 0 && A[0] < A[j]; j -= d)
A[j + d] = A[j];
A[j + d] = A[0];
}
}
}
}
3.交换排序
(1)冒泡排序
算法思想:每趟确定一个元素位置
空间复杂度:O(1)
时间复杂度:(有序)最好O(n);对比次数 n - 1;交换次数:0
(逆序)最坏O();对比(交换)次数 n(n - 1)/2;移动次数:3n(n - 1)/2;
平均O()
稳定性:稳定
适用性:顺序表,链表均可。
// 冒泡排序
void BubbleSort(int A[], int n){
int i, j, temp;
for(i = 0; i < n; i++){
bool flag = false; // 若有一趟为发生任何交换,则算法可以提前结束
for(j = n-1; j > i; j--){
if(A[j] < A[j-1]){
temp = A[j];
A[j] = A[j-1];
A[j-1] = temp;
flag = true;
}
}
if(flag == false) return;
}
}
(2)快速排序
算法思想:将序列不断“划分”,每次“划分”确定一个元素的位置(利用递归)
空间复杂度:O(递归层数) 故最好O() 最坏O(n)
时间复杂度:O( n * 递归层数) 故最好O(n) 最坏O() 平均O(n)
稳定性:不稳定
// 快速排序
void QuickSort(int A[], int low, int high){
if(low < high){
int pivotpos = Partition(A, low, high); // 划分
QuickSort(A, low, pivotpos - 1); // 划分左子表
QuickSort(A, pivotpos + 1, high); // 划分右子表
}
}
// 每一次“划分”
int Partition(int A[], int low, int high) {
int pivot = A[low];
while(low < high) {
while(low < high && A[high] >= pivot) --high; // 只要比pivot大且low<high,就一直循环
A[low] = A[high]; // 把比pivot小的值放在low处
while(low < high && A[low] <= pivot) ++low; // 只要比pivot小且low<high,就一直循环
A[high] = A[low]; // 把比pivot大的值放在high处
}
A[low] = pivot; // low = high时跳出循环,把pivot放在low或high处,位置确定
return low; // 返回最后pivot所在位置
}
3.选择排序
(1)简单选择排序
算法思想:每趟选择最小(或最大)的元素插入有序序列。
空间复杂度:O(1)
时间复杂度:O() 对比次数:【无论序列初始状态如何都是(n-1趟)】 n(n-1)/2
稳定性:不稳定
适用性:适用顺序表、链表
// 简单选择排序(选择最小加入有序序列)
void SelectSort(int A[], int n){
int i, j, min;
for(i = 0; i < n - 1; i++) {
min = i;
for(j = i + 1; j < n; j++)
if(A[j] < A[min])
min = j;
if(min != i){
temp = A[i];
A[i] = A[min];
A[min] = temp;
}
}
}
(2)堆排序
大根堆:根 >= 左、右 ;小根堆:根 <= 左、右
算法思想:将堆顶元素与待排序列最后一个元素交换位置,之后堆顶元素继续下坠。
时间复杂度:关键字对比字数不超过4n,建堆时间复杂度为O(n); 排序时间复杂度O(n);
所以时间复杂度(取大)为 O(n);
空间复杂度:O(1)
稳定性:不稳定
【注意】:堆插入元素:新元素放在最后位置,(按堆要求)再将新元素不断上升;
堆删除元素:将最后一个元素替换删除元素的位置,(按堆要求)再将该元素不断下坠。
// 建立大根堆(数组0存放哨兵)
void BuildMaxHeap(int A[], int len){
for(int i=len/2; i>0; i--) // 从后往前调整非终端结点
HeadAdjust(A, i, len);
}
// 将以k为根的子树调整为大根堆
void HeadAdjust(int A[], int k, int len){
A[0] = A[k];
for(int i = 2*k; i <= len; i*=2){
if(i < len && A[i] < A[i+1]) i++;
if(A[0] > A[i]) break;
else {
A[k] = A[i];
k = i;
}
}
A[k] = A[0];
}
// 堆排序完整逻辑
void HeapSort(int A[], int len){
BuildMaxHeap(A, len);
for(int i = len; i >1; i--){
int temp = A[i];
A[i] = A[1];
A[1] = temp;
HeadAdjust(A, 1, i-1);
}
}
4.归并排序
(2路归并)算法思想:每趟将相邻两个有序序列合并成有序的一个。
【注意】: m路归并,每次需要对比关键字m-1次。
时间复杂度:每趟O(n);趟数;故时间复杂度O(n);
空间复杂度:O(n)
稳定性:稳定
// 2路归并排序
int *B = (int *)malloc(n * sizeof(int)); //辅助数组B 和 A 大小一样
//A[low...mid]和A[mid+1...high]各自有序,将两个部分合并
void Merge(int A[], int low, int mid, int high){
int i,j,k;
for(k = low; k <= high; k++) {
B[k] = A[k]; //将A中元素全部赋值到B中
for(i = low, j = mid + 1; i <= mid && j <= high; k++){
if(B[i] <= B[j]) A[k] = B[i++];
A[k] = B[j++];
}
while(i<=mid) A[k++] = B[i++];
while(j<=high) A[k++] = B[j++];
}
// 对A进行完整的归并排序
void MergeSort(int A[], int low, int high){
if(low < high) {
int mid = (low + high) / 2; //从中间划分
MergeSort(A, low, mid);
MergeSort(A, mid+1, high);
Merge(A, low, mid, high);
}
}
5.基数排序
算法思想:初始化:把数据分成d元组,设置基数 r 个空队列;
分配:扫描所有关键字位,插入相应r队列;
收集:把各个队列中的结点依次出队并链接;
空间复杂度:O(r)
时间复杂度:O(d(n + r))
稳定性:稳定
适用性:1.关键字容易拆分,也d较小 2.每组关键字取值范围不大,r较小 3.数据个数n较大
二、外部排序(关注读写磁盘数)
优化外部排序:增加归并路数 k;--- 败者树 对比次数减少至
减少归并段 r。 --- 置换-选择排序
【重要结论】归并过程中的磁盘I/O次数 = 归并树的WPL * 2
严格的k路平衡归并可能需要补充虚段0:
若(n - 1)%(k - 1) = 0 : 不需要补充
若(n - 1)%(k - 1) = u : 补充(k - 1)- u 个虚段。