直接插入排序的思想:
void InsertSort() {
int i, j;
for (i = 2; i <= len; ++i) {
if (arr[i] < arr[i - 1]) {
arr[0] = arr[i]; //复制为监视哨
arr[i] = arr[i - 1]; //
for (j = i - 2; arr[0] < arr[j]; --j)
arr[j + 1] = arr[j]; //记录后移
arr[j + 1] = arr[0];
}
}
}
排序过程 监视哨 1 2 3 4 5 6 7 8 9 10
第0次排序: 0 49 28 65 76 13 27 36 58 11 17
第1次排序: 28 28 49 65 76 13 27 36 58 11 17 //从第2个数开始比较
第2次排序: 28 28 49 65 76 13 27 36 58 11 17
第3次排序: 28 28 49 65 76 13 27 36 58 11 17
第4次排序: 13 13 28 49 65 76 27 36 58 11 17
第5次排序: 27 13 27 28 49 65 76 36 58 11 17
第6次排序: 36 13 27 28 36 49 65 76 58 11 17
第7次排序: 58 13 27 28 36 49 58 65 76 11 17
第8次排序: 11 11 13 27 28 36 49 58 65 76 17
直接插入排序算法特点
算法分析
折半插入排序的思想:
算法分析
排序思想:
将记录序列分成若干子序列,分别对每个子序列进行插入排序
例如将 n 个记录分成 d 个子序列:
{R[1], R[1+d], R[1+2d], …, R[1+kd]}
{R[2], R[2+d], R[2+2d], …, R[2+kd]}
……………………………………
{R[d], R[d+d], R[d+2d], …, R[d+kd]}
其中 d 为增量,在排序过程中从大到小逐渐缩小,最后一趟排序减至1
排序结果 1 2 3 4 5 6 7 8 9 10 11
第0次排序 : 16 25 12 30 47 11 23 36 9 18 31
第1次排序(d=5): 11 23 12 9 18 16 25 36 30 47 31 (11-16 23-25 12-36...)
第2次排序(d=3): 9 18 12 11 23 16 25 31 30 47 36 (9-11-25-47 18-23-31-36...)
第3次排序(d=1): 9 11 12 16 18 23 25 30 31 36 37 (9 11 12 16...)
普通的冒泡排序:
void BubbleSort0(int arr[], int len) {
int i = 0, tmp = 0;
for (i = 0; i < len-1; i++) {
int j = 0;
for (j = 0; j < len-1-i; j++) {
if(arr[j] > arr[j+1]) {
tmp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = tmp;
}
}
}
}
a[]={1,2,3,4,5,6,7,8,10,9}
这组数据,按照上面的排序方式,第一趟排序后将10和9交换已经有序,接下来的8趟排序就是多余的,什么也没做。所以在交换的地方加一个标记,如果那一趟排序没有交换元素,说明这组数据已经有序,不用再继续下去。void BubbleSort1(int arr[], int len) {
int i = 0, tmp = 0;
for (i = 0; i < len-1; i++) {
int j = 0;
int flag = 0;
for (j = 0; j < len-1-i; j++) {
if(arr[j] > arr[j+1]) {
tmp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = tmp;
flag = 1; // 标记
}
}
if(flag == 0) return; // 没交换,已有序
}
}
(例如:1, 2, 3, 4, 7, 6, 5)
但是对于前面大部分是无序而后边小半部分有序的数据 (1, 2, 5, 7, 4, 3, 6, 8, 9, 10)
排序效率不高对于这种类型数据,我们可以记下最后一次交换的位置,后边没有交换,必然是有序的,然后下一次排序从第一个比较到上次记录的位置结束即可void BubbleSort2(int arr[], int len) {
int i = 0, tmp = 0, flag = 0;
int pos = 0, k = len-1;
for (i = 0; i < len-1; i++) {
int j = 0;
pos = 0;
flag = 0;
for (j = 0; j < k; j++) {
if(arr[j] > arr[j+1]) {
tmp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = tmp;
flag = 1; // 标记
pos = j; // 交换元素后,记录最后一次交换的位置
}
}
if(flag == 0) return; // 没交换,已有序
k = pos; // 下一次比较到记录位置即可
}
}
void BubbleSort3(int arr[], int len) {
int i = 0, j = 0, flag = 0;
int pos = 0, k = len-1;
int n = 0; // 同时找最大值的最小需要两个下标遍历
for (i = 0; i < len-1; i++) {
pos = 0;
flag = 0;
for (j = 0; j < k; j++) {
if(arr[j] > arr[j+1]) {
int tmp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = tmp;
flag = 1; // 标记
pos = j; // 交换元素后,记录最后一次交换的位置
}
}
if(flag == 0) return; // 没交换,已有序
k = pos; // 下一次比较到记录位置即可
for(j = k; j > n; j--) {
int tmp = arr[j];
arr[j] = arr[j-1];
arr[j-1] = tmp;
flag = 1;
}
n++;
if(flag == 0) return;
}
}
int Partition(int low, int high)
{
d[0] = d[low]; //用表的第一个记录作为枢纽记录
int pivotkey = d[low].number; //枢纽记录的关键字
while (low < high) { //从表的两端交替向中间扫描
while (low < high && d[high].number >= pivotkey) --high;
d[low] = d[high]; //将比枢纽记录关键字小的 移到低端
while (low < high && d[low].number <= pivotkey) ++low;
d[high] = d[low]; //将比枢纽记录关键字大的 移到高端
}
d[low] = d[0]; //枢纽记录归位
return low; //返回枢纽记录的位置
}
void QuickSort(int low, int high) {
if (low < high) {
int pos = Partition(low, high); //确定枢纽位置
QuickSort(low, pos - 1); //对左子序列排序,不包含枢纽
QuickSort(pos + 1, high); //对右子序列排序,不包含枢纽
}
}
枢纽0 | 1 2 3 4 5 6 7 8 9 10
排序前: 49 | 49 28 65 76 13 27 36 58 11 17 //以第一个记录49作为枢纽
排序后: 49 | 17 28 11 36 13 27 49 58 76 65
排序前: 17 | 17 28 11 36 13 27 //左子序列的第一个记录17作为枢纽,进行快排
排序后: 17 | 13 11 17 36 28 27
排序前: 13 | 13 11 //左子序列的左子序列快排
排序后: 13 | 11 13
排序前: 36 | 36 28 27 //左子序列的右子序列快排
排序后: 36 | 27 28 36
排序前: 27 | 27 28 //左子序列的右子序列的右子序列快排
排序后: 27 | 27 28
排序前: 58 | 58 76 65 //右子序列快排
排序后: 58 | 58 76 65
排序前: 76 | 76 65 //右子序列的右子序列快排
排序后: 76 | 65 76
算法分析
算法改进
略
堆排序的来源
n个元素的关键字序列 R[1].key, R[2].key, … ,R[n].key
写成完全二叉树结构时,若根为R[i].key
,则 左子树为R[2*i].key
,右子树为R[2*i+1].key
小根堆:根比左子树和右子树都小(R[i].key ≤ R[2*i].key && R[i].key ≤ R[2*i+1].key)
大根堆:根比左子树和右子树都大(R[i].key ≥ R[2*i].key && R[i].key ≥ R[2*i+1].key)
堆排序的思想
void HeapAdjust(int s, int m) {
int j;
arr[0] = arr[s]; //暂存堆顶到arr[0]
for (j = 2 * s; j <= m; j *= 2) {
if (j < m && arr[j] < arr[j + 1]) ++j; //横比 j最初指向左子树
if (arr[0] >= arr[j]) break; //纵比 定位
arr[s] = arr[j]; s = j;
}
arr[s] = arr[0]; //将调整前的堆顶记录插到位置s处
}
void HeapSort() {
int i, temp;
for (i = len / 2; i > 0; --i) //建堆
HeapAdjust(i, len);
for (i = len; i > 1; --i) {
temp = arr[i];
arr[i] = arr[1];
arr[1] = temp;
HeapAdjust(1, i - 1);
}
}
算法分析
算法思想
算法分析
对长度为 n 的记录进行2-路归并排序
k-路归并排序需要进行 l o g k n log_{k}n logkn 趟,时间复杂度 O ( n l o g k n ) O(nlog_{k}n) O(nlogkn)
插入排序
快速排序
堆排序
归并排序(2-路归并)