目录
#说明:以下所有排序算法(除了“堆排序”) 都是实现递增,数组下标从0开始。
1、插入排序
1.1直接插入排序
- 时间复杂度:O(n²)
- 空间复杂度:O(1)
- 稳定
- 可用于链式存储
(1)基于顺式存储
//插入排序
void InsertSort_in(int A[], int n) {
int i, j, temp;
for (i = 1; i < n; i++) {
temp = A[i];
for (j = i - 1; j >= 0 && A[j] > temp; j--) //为保证稳定性,不可以移动等于temp的元素
A[j + 1] = A[j]; //从i左边的元素到所有比temp大的元素都需要右移
A[j + 1] = temp;
}
}
(2)基于链式存储
//基于带头结点的链式存储的直接插入排序
void InsertSort_L(LinkList L) {
node *p = L->next;//p结点为待处理结点
node *q = L;//q结点为p的前驱结点,用于恢复连接
while (p) {
node *s = L->next;//s前驱即为插入位置
node *t = L;//保存s前驱
while (s != p && s->data <= p->data) {
/*
寻找插入位置,为保证稳定性必须带上等号;
注意此时while条件不能是s!=NULL,因为我们只需要判断待处理结点之前结点
*/
t = s;
s = s->next;
}
q->next = p->next;//先把连接续上
p->next = t->next;//把插入位置后继连上
t->next = p;//把插入位置先驱连上
//下面一行是方法一,这样会导致每次新处理的结点都从插入的位置开始,增加很多次无用循环
//q = p; p = p->next;
//下面五行是方法二,核心思想是直接处理q的后继结点,但需注意的是,q的后继也有可能是处理过的p,所以需要判断一下。
if (p == q->next) {
q = q->next;
p = q->next;
}
else p = q->next;
}
cout << count << endl;
}
1.2折半插入排序
- 时间复杂度:O(n²)
- 空间复杂度:O(1)
- 稳定
- 不可用于链式存储
//折半插入排序
void BiInsertSort(int A[], int n) {
int i, j, temp, low, high, mid;
for (i = 1; i < n; i++) {
temp = A[i];
low = 1; high = i - 1;
while (low <= high) {
mid = (low + high) / 2;
if (A[mid] > temp)high = mid - 1;
else low = mid + 1; //注意:为了保证稳定性,此时就算A[mid]==temp也查找右子表
}
for (j = i - 1; j >= high + 1; j--) //此时high+1就是low
A[j + 1] = A[j];
A[high + 1] = temp;
}
}
1.3希尔排序
- 时间复杂度:O(n²)
- 空间复杂度:O(1)
- 不稳定
- 不可用于链式存储
//希尔排序
void ShellSort(int A[], int n) {
int d, i, j, temp;
for (d = n / 2; d >= 1; d/=2) {//首次步长为n/2,以后每减半,最后一轮即d=1,相当于直接插入排序
for (i = d; i < n; i++) {
temp = A[i];
for (j = i - d; j >= 0 && temp < A[j]; j -= d)
A[j + d] = A[j];//记录后移
A[j + d] = temp;//退出循环时j已经减过d了,所以加回d才是插入的位置
}
}
2、交换排序
2.1冒泡排序
- 时间复杂度:O(n²)
- 空间复杂度:O(1)
- 稳定
- 可用于链式存储
(1)基于顺式存储
void swap(int &a, int &b) {
int temp;
temp = a;
a = b;
b = temp;
}
//冒泡排序
void BubbleSort(int A[], int n) {
int i, j;
for(i=0;i<n-1;i++){ //n个元素只需要n-1趟即可
bool flag = false;
for (j = 1; j < n - i; j++) {
if (A[j-1] > A[j]) {//为保证稳定性,不能带上等号
swap(A[j-1], A[j]);
flag = true;
}
}
if (flag == false) return;
}
}
(2)基于链式存储
void swap_L(node* s) {//交换a指向的下一个元素和下下一个元素
if (s == NULL)return;
node *a = s->next;
if (a == NULL)return;
node *b = a->next;
if (b == NULL)return;
s->next = b;
a->next = b->next;
b->next = a;
}
//基于带头结点的链式存储的冒泡排序
void BubbleSort_L(LinkList L,int n) {
node *p = L;
node *s;
for (int i = 0; i < n - 1;i++) {//遍历的趟数=结点数-1
bool flag = false;
for (s = L; s->next != NULL && s->next->next != NULL; s = s->next) {
if (s->next->data > s->next->next->data) {
swap_L(s);
flag = true;
}
}
if (flag == false) return;
}
}
2.2快速排序
- 时间复杂度:O(n²)
- 空间复杂度:O(n)
- 不稳定
- 不可用于链式存储
//快速排序
void QuickSort(int A[],int low,int high){
if (low < high) {
//把A数组中随机一个元素和A[1]交换 //可以对快排进行优化
int pivot = A[low];//选取第一个元素作为枢轴
int n1 = low, n2 = high;
while (n1 < n2) {
while (n1 < n2&&A[n2] >= pivot)n2--;//两个while判断条件必须加上等号,否则会相互死循环
A[n1] = A[n2];
while (n1 < n2&&A[n1] <= pivot)n1++;
A[n2] = A[n1];
}
A[n1] = pivot;
QuickSort(A, low, n1 - 1);
QuickSort(A, n1 + 1, high);
}
}
3、选择排序
3.1简单选择排序
- 时间复杂度:O(n²)
- 空间复杂度:O(1)
- 不稳定
- 可用于链式存储
- 需要对比关键字次;元素交换次数<n-1
//简单选择排序
void SelectSort(int A[], int n) {
for (int i = 0; i < n - 1; i++) {//一共进行n-1趟
int min = i;
for (int j = i + 1; j < n; j++)
if (A[j] < A[min])min = j;
swap(A[i], A[min]);
}
}
3.2堆排序
- 时间复杂度:
- 空间复杂度:O(1)
- 不稳定
- 可用于链式存储
- 每“下坠一层”,最多只需对比关键字2次;若树高为h,某结点在第i层,则将这个结点向下调整最多只需要“下坠”h-i层,关键字对比次数不超过2(h-i)
- 其中建堆时间复杂度=O(n)
(1)大根堆
//大根堆
//将以k为根的子树调整为大根堆
void MaxHeapAdjust(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++;//若存在比左孩子大的右孩子,则把i调整为右孩子标号
if (A[0] >= A[i]) break;//直接拜拜,不用再下坠了
else {
A[k] = A[i];
k = i;//转化成调整以i为根的子树
}
}
A[k] = A[0];
}
//建立大根堆
void BuildMaxHeap(int A[], int len) {
for (int i = len / 2; i > 0; i--)
MaxHeapAdjust(A, i, len);
}
//堆排序
void MaxHeapSort(int A[], int len) {
BuildMaxHeap(A, len);
for (int i = len; i > 1; i--) {
swap(A[i], A[1]);//将根结点换到第i位
MaxHeapAdjust(A, 1, i - 1);//待排元素减一,更新根结点
}
}
(2)小根堆
//小根堆
void MinHeapAdjust(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 MinHeapSort(int A[],int len) {
for (int i = len / 2; i > 0; i--)
MinHeapAdjust(A, i, len);
for (int i = len; i > 1; i--) {
swap(A[i], A[1]);
MinHeapAdjust(A, 1, i-1);
}
}
(3)堆的插入
核心思想:插入到堆底,然后再不断“上升”,直到无法继续上升为止。
//插入值为x的结点-基于小根堆实现
void HeapInsert(int A[], int &len, int x) {
int k = len+1; A[k] = x;
A[0] = A[k];
for (int i = k/2; i > 0; i /= 2) {
if (A[0] >= A[i])break;
else {
A[k] = A[i];
k = i;
}
}
A[k] = A[0];
len++;
}
(4)堆的删除
核心思想:被删元素用堆底元素代替,然后再不断“下坠”,直到无法继续下坠为止。
//删除编号为num的结点(数组下标从1开始)-基于小根堆实现
void HeapDelete(int A[], int &len, int num) {
int k = num; A[k] = A[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];
len--;
}
4归并排序
- 时间复杂度:
- 空间复杂度:O(n)【链式存储为O(1)】
- 稳定
- 可用于链式存储
(1)基于顺式存储
//归并排序
int *B = (int *)malloc(sizeof(int)*n);//辅助数组
void Merge(int A[], int low, int mid, int high) {
int i, j, k;
for (k = low; k <= high; k++)
B[k] = A[k];//将待排序元素复制到辅助数组里
for (i = low, j = mid + 1, k = i; i <= mid && j <= high; k++) {
if (B[i] <= B[j]) A[k] = B[i++];
else A[k] = B[j++];
}
while (i <= mid)A[k++] = B[i++];
while (j <= high)A[k++] = B[j++];
}
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);
}
}
(2)基于链式存储
//基于带头结点的链式存储的归并排序
LinkList Merge_L(LinkList l, LinkList r) {
node *k, *L;
if (l->data <= r->data) {
L = k = l;
l = l->next;
}
else {
L = k = r;
r = r->next;
}
while( l!= NULL && r != NULL) {
if (l->data <= r->data) {
k->next = l;
l = l->next;
}
else {
k->next = r;
r = r->next;
}
k = k->next;
}
if (l != NULL)k->next = l;
if (r != NULL)k->next = r;
return L;
}
LinkList MergeSort_L(LinkList L) {
if (L->next == NULL)return L;
node *fast = L, *slow = L, *pre=NULL;
while (fast != NULL && fast->next != NULL) {
pre = slow;
slow = slow->next;
fast = fast->next->next;
}
pre->next = NULL;//必须把链表拆开,合并的时候再接上
LinkList l = MergeSort_L(L);
LinkList r = MergeSort_L(slow);
return Merge_L(l, r);
}
5基数排序
- 时间复杂度:O(d(n+r))【关键字由d元组组成,即d趟,每趟分配O(n),收集O(r)】
- 空间复杂度:O(r)【r为最大基数】
- 稳定
- 可用于链式存储
- 应用场景:①数据元素的关键字可以方便地拆分为d组,且d较小;②每组关键字的取值范围不大,即r较小;③数据元素个数n较大。
- 例:①不适合给5个人的身份证号排序;②适合给10亿人的身份证号排序;③不适合给中文人名排序。
//基数排序
void RadixSort(int A[], int n) {
queue<int> q[10];
queue<int> L;
for (int i = 0; i < n; i++)
L.push(A[i]);
int flag = 1;
for (int x = 10;; x *= 10) {
while (!L.empty()) {
int k = L.front() % x / (x / 10);
if (k != 0)flag = 0;
q[k].push(L.front());
L.pop();
}
for (int i = 0; i < 10; i++) {
while (!q[i].empty()) {
L.push(q[i].front());
q[i].pop();
}
}
if (flag == 1)break;
flag = 1;
}
for (int i = 0; i < n; i++) {
A[i] = L.front();
L.pop();
}
}