基本概念
1.排序:使元素按关键字有序排列
2.内部排序:待排记录都在内存当中
3.稳定与不稳定:待排序元素中的两个元素a,b,a在b之前且a的关键字与b的关键字相同,排序后二者的相对位置不变,则称排序方法为稳定的,否则称为不稳定的。
存储结构:
typedef struct {
KeyType key;
//... other infomation
}RedType; //记录类型 Record Type
typedef struct{
RedType r[MAXSIZE + 1]; // 0 号作哨兵
int lenght;
}SqList;
插入排序
- 直接插入排序 O ( n 2 ) O(n^2) O(n2),稳定
void InsertSort(SqList &L){
int i,j;
for(i = 2; i < n; i++){
if(L.r[i].key < L.r[i-1].key){
L.r[0] = L.r[i]; //哨兵
L.r[i] = L.r[i-1]; //i-1后移
for(j = i - 2; L.r[j].key > L.r[0].key; j--)
L.r[j+1] = L.r[j]; //后移
L.r[j+1] = L.r[0]; //插入到正确位置
}
}
}
- 折半插入排序
折半寻找插入位置。比较次数为 O ( n l o g n ) O(nlogn) O(nlogn),移动次数没有减少,时间复杂度仍为 O ( n 2 ) O(n^2) O(n2)
void BinInsertSort(SqList &L){
for(int i = 2; i <= L.length; i ++){
int low = 1,high = i - 1;
L.r[0] = L.r[i]; //哨兵
while(low <= high){
int mid = (low + high)/2;
if(L.r[mid].key < L.r[0].key) low = mid + 1;
else high = mid - 1;
//不考虑key相等是稳定的
}
//退出循环时,low > high, low 是要插入的位置
for(int j = i - 1; j >= low; j--)L.r[j+1] = L.r[j];
L.r[low] = L.r[0];
}
}
- 希尔排序
将待排序列按除d同余划分为若干等价类,在类内排序,d逐渐减小,保证最后d = 1。
原理:对于有序序列,插入排序的时间复杂度为O(n);
希尔排序的时间复杂度 O ( n 1.5 ) O(n^{1.5}) O(n1.5)左右,不稳定;
void ShellInsert(SqList &L,int dk){
int i,j;
for(i = dk + 1; i <= L.length; i++){ //前dk个元素分组
L.r[0] = L.r[i];
for(j = i - dk;j > 0&&L.r[j].key > L.r[0].key; j-=dk) //j > 0,考虑当前元素最小
L.r[j+dk] = L.r[j];
L.r[j+dk] = L.r[0];
}
}
void ShellSort(SqList &L,int d[],int t){
for(int i = 0; i < t; i++)
ShellInsert(L,d[t]); //保证增量序列互质,且d[t-1] = 1
}
交换排序
- 冒泡排序(气泡排序),稳定
void BubbleSort(SqList &L){
for(int i = 1; i < L.length; i++){ //n - 1 趟 , L.r[L.lenth]有元素
for(int j = 2; j <= L.length - i + 1; j ++){
if(L.r[j-1].key > L.r[j].key)
swap(L.r[j-1],L.r[j]);
}
}
}
- 快速排序
不稳定,A(n) = O(nlogn);
最坏情况,序列有序,枢轴总是取low, O ( n 2 ) O(n^2) O(n2);
int Partition(SqList &L,int low,int high){ //返回枢轴的位置
L.r[0] = L.r[low];
int pivotkey = L.r[low].key;
while(low < high){
while(low < high&&L.r[high].key >= pivotkey)high--;
L.r[low] = L.r[high];
while(low < high&&L.r[low].key <= pivotkey)low++;
L.r[high] = L.r[low];
}
L.r[low] = L.r[0]; //low == high
return low;
}
void QuickSort(SqList &L,int low ,int high){
if(low < high){
int m = Partition(L,low,high);
QuickSort(L,low,m-1);
QuickSort(L,m+1,high);
}
}
选择排序
- 简单选择排序
O
(
n
2
)
O(n^2)
O(n2),稳定
每趟挑出第i小的元素,放在i的位置。
void SelectSort(SqList &L){
for(int i = 1; i <= L.length; ++ i){ //第i小的元素
int min = i;
for(int j = i+1; j <=L.length; ++j){
if(L.r[j].key < L.r[min].key)
min = j;
}
swap(L.r[i],L.r[min]);
}
}
- 堆排序:
最坏最后都是 O ( n l o g n ) O(nlogn) O(nlogn),不稳定,n较大时使用
堆:完全二叉树
大根堆:
a i ⩾ a 2 i , a i ⩾ a 2 i + 1 , a_i \geqslant a_{2i},a_i \geqslant a_{2i+1}, ai⩾a2i,ai⩾a2i+1,
小根堆:
a i ⩽ a 2 i , a i ⩽ a 2 i + 1 , a_i \leqslant a_{2i},a_i \leqslant a_{2i+1}, ai⩽a2i,ai⩽a2i+1,
存储结构:
因为是完全二叉树,可用顺序表存储
R[MAXSIZE];
调整操作:
void HeapAdjust(int R[],int i,int n){//大根堆调整
//调整操作的前提是,除去调整结点,调整结点的左右树都是堆
//R[i+1...n]都满足堆定义,R[i]是要调整的结点。
int rc = R[i];
for(int j = 2*i; j<=n; j*=2){ //*2跳到下一层
if(j < n && R[j+1] > R[j] )j++;
if(rc>=R[j])break;
R[i] = R[j]; i = j;
}
R[i] = rc;
}
void HeapSort(int R[],int n){ //大根堆得升序数组
for(int i = n/2; i >=1 ; --i)
HeapAdjust(R,i,n);
for(int i = 0; i < n; i++){ //n-1次排序
swap(R[1],R[n-i]);
HeapAdjust(R,1,n-i-1); //排完序输出数组即可
}
}
归并排序
void MSort(RcdType s[],int l,int r){
if(l < r){
int m = (l+r)/2;
MSort(s,l,m);
MSort(s,m+1,r);
Merge(s,l,m,r);
}
}
void Merge(RcdType s[],int l,int m,int r){
RcdType t[MAXSIZE] = {0}; //临时数组
int i,j,k = 0;
for( i = l,j = m + 1; i <= m && j <=r;){ //合并两个升序数组
if(s[i].key < s[j].key)
t[k++] = s[i++];
else
t[k++] = s[j++];
}
while(i<=m)t[k++] = s[i++];
while(j<=r)t[k++] = s[j++];
for(i = l,k = 0;i <= r; i ++,k++) //拷贝回s;
s[i] = t[k];
}
void MergeSort(SqList &L){
MSort(L.r,1,L.length);
}
基数排序
桶排序,桶可以用队列实现。
不通过比较,而是分配收集的方法实现排序,稳定
O(k*(n+m)); k 维,每维分配n次,收集m次
典例:按年月日排序,极快。
扑克排序:花色桶*4,序号桶1-13。
如果想一次解决,就设max个桶,耗空间。
void RadixSort(int a[],int max,int n){
vector<queue<int> > v(10);
int d = 1;
while(max/d){
d*=10;
for(int i = 0; i < n; i++) //分配
v[a[i]%d].push(a[i]);
int k = 0;
for(int i = 0 ; i < 10; i++){ //收集
while(v[i].size()){
a[k++] = v[i].front();
v[i].pop();
}
}
}
}
总结