目录
0总览
参考1:数据结构教程——清华大学出版社
参考2:https://www.cnblogs.com/l199616j/p/10742603.html
参考3:https://cloud.tencent.com/developer/article/1505445
参考4:https://blog.csdn.net/weixin_46554582/article/details/119172449
1、内部排序方法比较
排序方法 | 最好情况 | 平均时间 | 最坏情况 | 辅助空间 | 稳定性 | 复杂性 |
直接插入 | O(n) | O(n^2) | O(n^2) | O(l) | 稳定 | 简单 |
折半插入 | O( | O( | O( | O(l) | 稳定 | 简单 |
直接选择 | O(n^2) | O(n^2) | O(n^2) | O(l) | 不稳定 | 简单 |
冒泡排序 | O(n) | O(n^2) | O(n^2) | O(l) | 稳定 | 简单 |
希尔排序 | O( | O( | O( | O(l) | 不稳定 | 较复杂 |
快速排序 | O( | O( | O(n^2) | O( | 不稳定 | 较复杂 |
二路归并 | O( | O( | O( | O(n) | 稳定 | 简单 |
堆排序 | O( | O( | O( | O(l) | 不稳定 | 较复杂 |
树形选择 | O( | O( | O( | O(n) | 不稳定 | 较复杂 |
基数排序 | O(2mn) | O(2mn) | O(2mn) | O(dn) | 稳定 | 较复杂 |
1、堆排序的辅助空间的要求比快速排序低、并且堆排序不会出现快速排序最差的情况。
2、快速排序的平均性能最好,遇到逆序列时退化为冒泡排序。
3、希尔排序借鉴了插入排序,将序列分成多个部分,每一个部分记录个数较少,排序较快,随着间隔的减少,分组数的下降,每组记录增多,但是由于各序列已基本有序,所以插入序列仍然比较快。着使得希尔排序比插入排序效率高,。
4、归并排序和冒泡排序这类算法建立在逐个比较的基础上,能够保证稳定性。
2、选择
(1)数据规模较大,选择平均时间较短的算法,优先考虑快速排序,堆排序,归并排序,树形选择和希尔排序,他们的平均时间夫复杂度为O() 。
数据规模比较小,选择简单的算法,优先考虑插入排序、交换排序、直接选择排序。
(2)稳定性反映着算法的健壮性,快速排序、希尔排序、堆排序、选择排序不是稳定的排序算法。基数排序、冒泡排序、插入排序、归并排序是稳定排序算法。
(3)初始记录基本有序,可以看最好情况复杂度较小的,可选用直接插入、堆排序、冒泡排序、归并排序、希尔排序。其中插入和冒泡是最快的,时间夫复杂度为O(n)。
初始记录基本无序,最好使用快速排序、希尔排序、简单选择排序。
(4)空间复杂度过高、排序规模较大时,避免使用归并和树形等空间复杂度较高的算法。
(5)记录所占内存空间比较大时,尽量选择移动次数比较少的算法,如直接选择排序比直接插入排序更合适。
一、插入排序
插入排序就是,从第二个元素开始,依次和前面的比较,如果比前面的元素小,则插入进去。
基本流程是:
step1:第二个元素是否比第一个元素小?是,则插到第一个元素前面;否,保证位置不变。
step2:第三个元素是否比第二个元素小,是,是否比第一元素小...
依次类推
(1)直接插入排序
#include <iostream>
#include <string>
using namespace std;
#define MAXSIZE 100 //顺序表最大长度
typedef int KeyType;
typedef int DataType;
struct EType {
KeyType key; //关键字项
DataType elem;
};
struct LinearList {
EType r[MAXSIZE + 1];
int length;
};
//直接插入排序
void InsertSort(LinearList& L) {
for (int i = 1; i < L.length; i++) {
//向r[I]中插入r[0:I-1]
EType t = L.r[i];//保存本次需要插入的元素
int j;
for (j = i - 1; j > 0 && t.key < L.r[j].key; j--)//顺次往前比较
L.r[j + 1] = L.r[j];//数据元素后移
L.r[j + 1] = t;//插入
}
}
(2)折半排序
#include <iostream>
#include <string>
using namespace std;
#define MAXSIZE 100 //顺序表最大长度
typedef int KeyType;
typedef int DataType;
struct EType {
KeyType key; //关键字项
DataType elem;
};
struct LinearList {
EType r[MAXSIZE + 1];
int length;
};
//折半插入算法
void BinaryInsert(LinearList& L) {
EType t;
int Low, High, m, i, j;
for (i = 1; i < L.length; i++) {
t = L.r[i];//保存本次需要插入的元素
Low = 0; High = i - 1;//设置范围
while (Low <= High) {
//若还在插入范围内,则继续插入
m = (Low + High) / 2;//设置范围中点
if (t.key < L.r[m].key) {
//当前点与中点点比较,如果小的话
High = m - 1;
}
else {
Low = m + 1;
}
for (i = i - 1; j >= High + 1; j++)
L.r[j + 1] = L.r[j];//顺序移动
L.r[j + 1] = t;
}
}
}
(3)希尔排序
希尔排序是,把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。
#include <iostream>
#include <string>
using namespace std;
#define MAXSIZE 100 //顺序表最大长度
typedef int KeyType;
typedef int DataType;
struct EType {
KeyType key; //关键字项
DataType elem;
};
struct LinearList {
EType r[MAXSIZE + 1];
int length;
};
//希尔排序
void ShellSort(LinearList& L, int d[], int number) {
//用希尔排序法堆记录r[0]-r[n-1]排序,d是增量值数组
//number是增量值的个数,各组内采用直接插入法排序
int i, j, k, m, Span;
EType t;
for (m = 0; m < number; m++) {
Span = d[m];
for (k = 0; k < Span; k++) {
for (i = k + Span; i < L.length; i++) {
t = L.r[i];
j = i - Span;
while (j > -1 && t.key < L.r[j].key) {
L.r[j + Span] = L.r[j];
j -= Span;
}
}
L.r[j + Span] = t;
}
}
}
二、交换排序
(1)冒泡排序
冒泡排序就是,相邻元素相互比较,然后交换顺序
基本流程是:
step1:第一个与第二个比较,第二个与第三个比较
step2:经过step后,相邻元素发生变化了,从第二个元素开始比较,第二个与第三个比较,第三个与第四个比较
依次类推
#include <iostream>
#include <string>
using namespace std;
#define MAXSIZE 100 //顺序表最大长度
typedef int KeyType;
typedef int DataType;
struct EType {
KeyType key; //关键字项
DataType elem;
};
struct LinearList {
EType r[MAXSIZE + 1];
int length;
};
void BublleSort(LinearList& L) {
int i, j, change;
change = 1;
j = L.length - 1;
while (j > 0 && change) {
change = 0;
for (i = 0; i < j; i++) {
if (L.r[i].key > L.r[i + 1].key) {
//大于下一个数
EType temp = L.r[i];
L.r[i] = L.r[i + 1];
L.r[i + 1] = temp;
change = 1;
}
}
j--;
}
}
(2)快速排序
用数组的第一个数作为关键数据,然后将所有比它小的数都放到它左边,所有比它大的数都放到它右边,这个过程称为一趟快速排序。整个数组找基准正确位置,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面。
#include <iostream>
#include <string>
using namespace std;
#define MAXSIZE 100 //顺序表最大长度
typedef int KeyType;
typedef int DataType;
struct EType {
KeyType key; //关键字项
DataType elem;
};
struct LinearList {
EType r[MAXSIZE + 1];
int length;
};
//快速排序
int Partition(EType r[], int low, int high) {
//对记录元素r[low...high]进行一次快速排序,并将本范围的元素按标准元素分为两部分
//且标准元素在这两部分之间
KeyType StandardKey;
EType temp;
temp = r[low];//标准元素放中间
StandardKey = r[low].key;//该范围的标准元素
while (low < high) {
//从表的两端交替地向中间扫描
while (low < high && r[high].key >= StandardKey) {
//大于标准元素
high--;
r[low++] = r[high];//小于标准元素的值往前放
}
while (low < high && r[low].key >= StandardKey) {
//大于标准元素
low++;
r[high--] = r[low];//大于标准元素的值往后放
}
}
r[low] = temp;//标准元素移到正确位置
return low;//返回标准位置
}
void QSrot(EType r[], int low, int high) {
//对记录元素r[low...high]进行快速排序
int StandardLoc;
if (low < high - 1) {
StandardLoc = Partition(r, low, high);
QSrot(r, low, StandardLoc - 1);
QSrot(r, StandardLoc - 1,high);
}
}
void QuickSort(LinearList& L) {
QSrot(L.r, 0, L.length - 1);
}
三、选择排序
选择排序就是,第一个元素与后面所有元素相比较,
基本流程是:
step1:第一个与第二个比较,第二个与第三个比较
step2:经过step后,相邻元素发生变化了,从第二个元素开始比较,第二个与第三个比较,第三个与第四个比较
依次类推
(1)直接选择排序
#include <iostream>
#include <string>
using namespace std;
#define MAXSIZE 100 //顺序表最大长度
typedef int KeyType;
typedef int DataType;
struct EType {
KeyType key; //关键字项
DataType elem;
};
struct LinearList {
EType r[MAXSIZE + 1];
int length;
};
//直接选择排序算法
void SelectSort(LinearList& L) {
int i, j, Small;
EType Temp;
for (i = 0; i < L.length; i++) {//控制排序的次数
Small = i;//标记当前查找到的关键字最小的记录在数组中的位置
for (j = i + 1; j < L.length;j++) {//当前关键字最小的的记录
if (L.r[j].key < L.r[Small].key) {
Small = j;
}
}
if (Small != i) {
Temp = L.r[i];
L.r[i] = L.r[Small];
L.r[Small] = Temp;
}
}
}
(2)堆排序
#include <iostream>
#include <string>
using namespace std;
typedef char EType;
struct MaxHeap {
EType* heap;
int HeapSize;
int MaxSize;
};
//初始化一个非空的最大堆算法
void MaxHeapInit(MaxHeap& H) {
//从堆中的数据初始化一个最大堆
for (int i = H.HeapSize / 2; i >= 1; i--) {
H.heap[0] = H.heap[i];//将子树根节点的值复制到工作空间heap[0]
int son = 2 * i;
while (son <= H.HeapSize) {
//找到左右孩中较大的节点
//son <= H.HeapSize时,存在右孩子,如果左孩子小于右孩子,son指向右孩子
if (son < H.HeapSize && (H.heap[son] < H.heap[son + 1])) {
son++;
}
//大孩子与工作空间的节点值再比较,工作空间的值较大,找到Heap[0]的目标位置
if (H.heap[0] >= H.heap[son]) break;
H.heap[son / 2] = H.heap[son];//将大孩子上移动到双亲位子
son *= 2;//son下移一层到上移的节点(大孩子位置)
}
H.heap[son / 2] = H.heap[0];//heap[0]存放的到目标位置
}
}
bool MaxHeapInsert(MaxHeap& H, EType& x) {
//插入值为x的节点,到最大堆中,maxsize是数组最大的容量
if (H.HeapSize == H.MaxSize)
return false;//数据空间已满
int i = ++H.HeapSize;//i为插入节点后的节点个数
while (i != 1 && x > H.heap[i / 2]) {
H.heap[i] = H.heap[i / 2];
i = i / 2;//节点下移
}
H.heap[i] = x;
return true;
}
//最大堆中删除堆顶节点,并放入x中算法
bool MaxHeapDelete(MaxHeap& H, EType& x) {
if (H.HeapSize == 0) return false;
x = H.heap[1];//最大节点存放到x
H.heap[0] = H.heap[H.HeapSize--];//最后一个节点放在heap[0],调整堆中元素的个数
int i = 1, son = 2 * i;
while (son <= H.HeapSize) {
//找到左右孩中较大的节点
//son <= H.HeapSize时,存在右孩子,如果左孩子小于右孩子,son指向右孩子
if (son < H.HeapSize && (H.heap[son] < H.heap[son + 1])) {
son++;
}
//大孩子与工作空间的节点值再比较,工作空间的值较大,找到Heap[0]的目标位置
if (H.heap[0] >= H.heap[son]) break;
H.heap[i] = H.heap[son];//孩子上移
i = son; //下移节点指针,继续比较
son *= 2;//son下移一层到上移的节点(大孩子位置)
}
H.heap[i] = H.heap[0];//heap[0]存放的到目标位置
return true;
}
//堆排序
void HeapSort(MaxHeap& H) {
//利用堆对H.heap[1:n]数组中的数据排序
EType x;
MaxHeapInit(H);
for (int i = H.HeapSize - 1; i >= 1; i--) {
MaxHeapDelete(H, x);
H.heap[i + 1] = x;
}
}
四、归并排序
归并排序就是,递归得将原始数组递归对半分隔,直到不能再分(只剩下一个元素)后,开始从最小的数组向上归并排序
Step1:向上归并排序的时候,需要一个暂存数组用来排序。
Step2: 将待合并的两个数组,从第一位开始比较,小的放到暂存数组,指针向后移,
Step3:直到一个数组空,这时,不用判断哪个数组空了,直接将两个数组剩下的元素追加到暂存数组里。
Step4:再将暂存数组排序后的元素放到原数组里,两个数组合成一个,这一趟结束。
(1)二路归并排序
#include <iostream>
#include <string>
using namespace std;
#define MAXSIZE 100 //顺序表最大长度
typedef int KeyType;
typedef int DataType;
struct EType {
KeyType key; //关键字项
DataType elem;
};
struct LinearList {
EType r[MAXSIZE + 1];
int length;
};
//归并排序
bool MergeSort(EType source[], int n) {
EType* result = new EType[n];
int size = 1;
while (size < n) {
Merge(source, result, size, n);//从a归并到b
size *= 2;
Merge(result, source, size, n);//从b归并到a
size *= 2;
}
}
//一次二路归并排序
void Merge(EType r[], EType swap[], int size, int n) {
int i, j, lb1, lb2, ub1, ub2, sp;
lb1 = 0;//第一个子文件序列的起始位置
sp = 0;
while (lb1 + size <= n - 1) {
//测试是否存在两个可以合并的子文件
lb2 = lb1 + size;//第二个子文件序列的起始位置
ub1 = lb2 - 1;//第一个子文件序列的结束位置
if (lb2 + size - 1 <= n - 1) {//第er个子文件序列的结束位置
ub2 = n - 1;
}
else
ub2 = lb2 + size - 1;
for (i = lb1, j = lb2; i <= ub1 && j <= ub2;) {
if (r[i].key <= r[j].key)
swap[sp++] = r[i++];
else
swap[sp++] = r[j++];
}
while (i <= ub1) {
//序列2已归并完成,将序列1中剩余的记录顺序存放搭配数组swap中
swap[sp++] = r[i++];
}
while (j <= ub2) {
//序列1已归并完成,将序列2中剩余的记录顺序存放搭配数组swap中
swap[sp++] = r[j++];
}
lb1 = ub2 + 1;
}
for (i = lb1; i < n;) {
//将元素序列中无法配对的子文件顺序存放到数组swap中
swap[sp++] = r[i++];
}
}
五、基数排序(桶排序)
将整数按位数切割成不同的数字,然后按每个位数分别比较。由于整数也可以表达字符串(比如名字或日期)和特定格式的浮点数,所以基数排序也不是只能使用于整数。
(1)二维数组桶排序
#include <iostream>
#include <string>
using namespace std;
//基数排序算法实现
#define MAXNUM 100
typedef int KeyType;
struct ETypeR {
KeyType key;
};
void RadixSort1(ETypeR r[],int n,int m) {
int i, j, k, Number, power, h;
int C[10];//对应桶的计数器
ETypeR d[10][MAXNUM];
k = 1; power = 1;
while (k <= m) {
if (k == 1) {
power = 1;
}
else {
power = power * 10;
}
for (i = 0; i < 10; i++) {
C[i] = 0;//清空各桶
}
for (i = 0; i < n; i++) {
//各记录放到相应的桶中
j = r[i].key / power - (r[i].key / (power * 10)) * 10;
d[j][C[j]] = r[i];
C[j] = C[j] + 1;
Number = 0;
for (h = 0; h < 10; h++) {
//从桶中往回收集
if (C[h] != 0) {
for (j = 0; j < C[h]; j++) {
r[Number] = d[h][j];
Number;
}
}
}
}
k++;
}
}
void main() {
ETypeR Test[10] = { 712,342,48,686,6,841,429,134,68,264 };
int length = 10,m = 3;
int i;
RadixSort1(Test, length, m);
cout << "\"" << endl;
for (i = 0; i < length; i++) {
cout << Test[i].key << "\"";
}
}
(2)链式存储结构桶排序
#include <iostream>
#include <string>
using namespace std;
//桶采用链式存储结构的基数排序
struct ETypeR2 {
EType data;
KeyType key;
};
struct radixnode {
EType data;
KeyType key;
radixnode* next;
};
struct radixhead {
int tub;
radixnode* link;
};
void RadixSort2(ETypeR2 r[], int n, int m) {
int i, j, k, l, power;
radixnode* p, *q;
radixhead head[10];
for (j = 0; j < 10; j++) {
//初始化桶结构
head[j].tub = j;
head[j].link = NULL;
}
power = 1;
for (i = 0; i < m; i++) {
//进行m次排序
if (i == 0)
power = 1;
else
power = power * 10;
for (l = 0; l < n; l++) {
q = new radixnode;
q->data = r[l].data;
q->key = r[l].key;
q->next = NULL;
k=r[l].key/power- (r[l].key / (power * 10)) * 10;
p = head[k].link;
if (p == NULL)
head[k].link = q;
else {
while (p->next != NULL)
p = p->next;
p->next = q;
}
}
l = 0;
for (j = 0; j < 10; j++) {
p = head[j].link;
while (p!=NULL) {
r[l].data = p->data;
r[l].key = p->key;
l++;
q = p->next;
delete p;
p = q;
}
head[j].link = NULL;
}
}
}