排序(二)
来源:互联网 发布:51秀啦网站源码 编辑:程序博客网 时间:2024/06/06 14:16
数据存储结构体,这里默认数据都是从下标1开始存储
const static int MAX = 1024;// 存储数据的机构体typedef struct{ int r[MAX]; int length;}SqList;
交换函数
// 交换两个数void swap(SqList *L, int i,int j){ int tmp = L->r[i]; L->r[i] = L->r[j]; L->r[j] = tmp;}
1.希尔排序
希尔排序其实就是插入排序的改进,插入排序可以看做每次比较的步长为1,而希尔排序设置一个初始的步长,步长迭代缩减,最后以1结束。希尔排序的复杂度主要和设置这个初始步长有关。当步长选择的比较差时复杂度就接近插入排序的复杂度。
这是插入排序的代码
void InsertSort(SqList *L){ int i,j; for(i=2;i<=L->length;i++)// 从第二个数开始依次比较 { if(L->r[i]<L->r[i-1]) { L->[0] = L->r[i];// 将当前数记录在r[0]位置 // 循环和前边数比较,确定应该插入的位置,并将该位置后边的数都后移一位 for(j=i-1; L->r[j]>L->r[0];j--) L->[j+1] = L->r[j]; L->r[j+1] = L->r[0];// 将该数插入对应位置 } }}
希尔排序则需要更改初始的比较步长,并在外部加一个循环,用来迭代步长,使步长最终减小为1.
希尔排序
void ShellSort(SqList *L){ int i,j; int step = L->length; do{ step = step/3+1; for(i=step+1;i<=L->length;i++)// 从第step+1个数开始依次比较 { if(L->r[i]<L->r[i-step])// 比较当前数和它前step位置的数大小 { L->[0] = L->r[i];// 将当前数记录在r[0]位置 // 循环和前边相隔step个数比较,确定应该插入的位置 for(j=i-step; j>0 && L->r[j]>L->r[0];j-=step) L->[j+step] = L->r[j];// 每次找到的比它大的数移动到后边位置 L->r[j+step] = L->r[0];// 将该数插入对应位置 } } } while(step>1);}
步长选取是希尔排序的关键,希尔排序是不稳定的排序算法,而且必须保证最后一次循环步长以1结束。
最好时间复杂度O(n),最坏O(n^2)
2.堆排序
堆是具有下列性质的完全二叉树:
- 每个节点的值都大于或等于其左右孩子的节点值,称为大顶堆;
- 每个节点的值都小于等于其左右孩子的节点值,称为小顶堆;
大顶堆和小顶堆使用层序遍历按顺序存储在数组中。
堆排序:利用大顶堆排序,首先将待排序的序列构造成一个大顶堆。此时堆顶元素就为最大的,将堆顶元素和末尾元素交换,然后调整前n-1个数为大顶堆,再将堆顶元素交换到n-1位置,继续将前n-2个数构调整成大顶堆。如此反复执行便能得到一个有序序列。
算法实现时主要需要解决两个问题:
- 由原始序列构建一个大顶堆
- 输出堆顶元素后,调整剩余元素为一个大顶堆
堆排序的代码如下
// 堆排序void HeapSort(SqList *L){ int i = 0; // 由初始序列构建大顶堆,从非叶子节点开始,从下到上调整每个非叶子结点 for (i = L->length / 2; i > 0; i--) { HeapAdjust(L, i, L->length); } // 依次取出堆顶元素调整大顶堆 for (i = L->length; i > 1; i--) { swap(L, 1, i); // 将堆顶元素换到末尾 HeapAdjust(L, 1, i - 1); // 将堆的元素个数进行-1,并将新交换的根节点重新调整堆为大顶堆 }}
这里两个for循环,第一个是由初始给的数组序列构建一个大顶堆,第二个是每次讲大顶堆堆顶元素和末尾元素交换,并调整剩余元素为一个大顶堆。
其中void HeapAdjust(SqList *L, int s,int length)
函数是初始假设L中除s节点外都满足大顶堆条件,然后从s节点开始调整为一个合法的大顶堆,将s节点与左右孩子进行比较,如果孩子比节点s值大,则将较大的孩子和s交换位置,继续判断交换后的s节点和他的子节点,代码如下
// 调整大顶堆void HeapAdjust(SqList *L, int s,int length){ // 初始假设L中除s节点外都满足大顶堆条件 int i = 0; int tmp = L->r[s]; for (i = 2 * s; i < length;i*=2) { // 判断i是否出界而且判断左右孩子哪个比较大,让i指向大的孩子 if (i < length && L->r[i] < L->r[i + 1]) i++; // 如果子结点的最大值小于需要判断的初始节点值,则表示初始节点放在该位置满足大顶堆条件,直接跳出 if (L->r[i] < tmp) break; // 将较大孩子的值赋给父节点 L->r[s] = L->r[i]; // 继续判断当前改动的节点对于他的孩子是否满足大顶堆条件,即记录下当前改动的节点 s = i; } // 将初始节点值放到对应位置 L->r[s] = tmp;}
复杂度分析
运行时间主要消耗在初始简历大顶堆和后边调整大顶堆上,总体的时间复杂度为O(nlogn),最坏O(nlogn),最好O(nlogn)。
稳定性:由于记录的比较与交换是跳跃式的,所以堆排序是一种不稳定的排序算法。
由于初始时建堆时需要的比较次数较多,所以不适合待排序列个数较少的情况。
3.归并排序
时间复杂度:平均:O(nlogn),最好:O(nlogn),最坏:O(nlogn)
稳定性:稳定
归并排序原理:假设有n个数的序列需要排序,首先进行两两比较排序,然后将相邻的两两子序列进行归并排序,一直归并到n/2子列,然后对1-n/2子列和n/2+1到length子列进行归并,得到有序数列n,这种排序方法称为2路归并排序。
归并排序的递归实现
// 合并TR2中两个子序列s-m和m+1-t到数组TR1中void Merge(int TR2[], int TR1[], int s, int m, int t){ int i, j, k; for (i = s, j = m + 1,k=s; i <= m&&j <= t;k++) { if (TR2[i]>TR2[j]) { TR1[k] = TR2[j]; j++; } else { TR1[k] = TR2[i]; i++; } } if (i <= m) { for (int l = i; l <= m; l++,k++) { TR1[k] = TR2[l]; //将剩余的i-m复制到TR1中 } } if (j <= t) { for (int l = j; l <= t; l++, k++) { TR1[k] = TR2[l]; // 将剩余的m+1 -- t复制到TR1中 } }}//SR数组归并排序为TR1数组void Msort(int SR[], int TR1[], int s, int t){ int TR2[MAX]; if (s == t) TR1[s] = SR[s]; else { int m = (s + t) / 2; // 将SR[s...t]平分为SR[s...m]和SR[m+1...t] Msort(SR, TR2, s, m); // 递归对SR[s...m]进行归并排序为TR2 Msort(SR, TR2, m + 1, t); // 递归对SR[m+1...t]进行归并排序为TR2 Merge(TR2, TR1, s, m, t); // 实际的归并排序 将TR2中s到m和m+1到t归并排序为TR1 }}// 归并排序void MergeSort(SqList *L){ Msort(L->r, L->r, 1, L->length);}
非递归实现
// 将数组SR中长度为k的两个相邻子列进行归并放入TR数组中void MergePass(int SR[], int TR[], int k, int length){ int i = 1; while (i <= length - (2 * k)+1) { Merge(SR, TR, i, i + k - 1, i + 2 * k - 1); i = i + 2 * k; } // 归并最后两个序列 if (i < length - k + 1) { Merge(SR, TR, i, i + k - 1, length); } else { // 剩下单个子序列 for (int j = i; j <= length; j++) { TR[j] = SR[j]; } }}// 归并排序非递归实现void MergeSort2(SqList *L){ int *TR = (int *)malloc(sizeof(int)*(L->length + 1)); // 申请一个辅助空间 int k = 1; while (k < L->length) { MergePass(L->r, TR, k, L->length); k = k * 2; MergePass(TR, L->r, k, L->length); k = k * 2; }}
4.快速排序
思想:快速排序通过一趟排序将数列划分为两个部分,前边的都比选中的关键字小,后边的都比选中的关键字大,然后继续对前后两部分分别进行排序,以达到整个序列有序。
递归实现代码如下
// 将L-r中low到high划分为两部分,并返回划分点的位置int Partition(SqList *L, int low, int high){ int pivotKey = L->r[low]; // 用子序列的第一个作为划分点 // 进行划分:这里使用两端交替向中间划分 while (low < high) { while (low < high && pivotKey <= L->r[high]) high--; swap(L, low, high); while (low < high && pivotKey >= L->r[low]) low++; swap(L, low, high); } return high;}// 对表中序列r[low-high]进行快速排序void Qsort(SqList *L, int low, int high){ int pivot; if (low < high) { pivot = Partition(L, low, high); // 将L->r[low-high]一分为二,前边都比L->r[pivot]小,后边都比这个大 Qsort(L, low, pivot - 1); // 对low - pivot-1这部分继续进行递归划分 Qsort(L, pivot + 1, high); // 进行递归划分 }}// 快速排序void QuickSort(SqList *L){ Qsort(L, 1, L->length);}
最坏情况下复杂度为O(n^2),平均时间复杂度为O(nlogn),最好O(nlogn)
稳定性:不稳定
- 排序(二):希尔排序
- 排序(二)快速排序
- 排序(二)----希尔排序
- 排序(二):选择排序
- 排序算法(二)
- 排序系列(二)
- 排序算法(二)
- 快速排序(二)
- 快速排序(二)
- 排序问题(二)
- 冒泡排序(二)
- List排序(二)
- 快速排序(二)
- 排序算法(二)
- 插入排序(二)
- 堆排序(二)
- 排序总结(二)
- 常用排序(二)
- Android 自定义代码快捷键和代码小技巧
- 判断一棵树是否是完全二叉树
- matlab保存数据到txt文档
- Mongodb新增的聚合方法及其Java客户端
- CNN光流计算--FlowNet: Learning Optical Flow with Convolutional Networks
- 排序(二)
- git操作命令
- 关于python爬取网页上指定内容
- 为控件设置某几个边的边框
- 服务机器人的普及与这三方面因素紧紧相关
- vulkan安装流程
- DOM对象,控制HTML元素
- Python3《机器学习实战》学习笔记(三):决策树实战篇之为自己配个隐形眼镜
- 强连通分量