数据结构:排序(Sort)【详解】_sort结构体排序-CSDN博客
一、概述
1. 排序定义:
将一个数据元素(或记录)的任意序列,重新排列成一个按关键字有序的序列
2. 排序分类
(1)按待排序记录所在位置:
内部排序:待排序记录存放在内存
外部排序:排序过程中需对外存进行访问的排序
(2)按排序依据原则:
插入排序:直接插入排序、折半插入排序、希尔排序
交换排序:冒泡排序、快速排序
选择排序:简单选择排序、堆排序
归并排序:2路归并排序
基数排序
(3)按排序所需工作量:
简单的排序方法:
先进的排序方法:
基数排序:
3.排序基本操作:
- 比较两个关键字大小
- 将记录从一个位置移动到另一个为止
考点:给出5个算法,写出对应的排序(25分)
二、插入排序
(一)直接插入排序
理论基础 —— 排序 —— 直接插入排序_将关键字6 9 1 5 8 4 7依次-CSDN博客
1.排序思想
直接插入排序(Straight Insertion Sort)的基本操作是依次将待排序序列中的每一个记录插入到已经排好序的有序表中,直到全部记录都排好序。
2.排序过程:
整个排序过程为n-1趟插入,即先将序列中第1个记录看成是一个有序的子序列,然后从第2个记录开始,逐个进行插入,直至整个序列有序。具体如下:
※ 依靠双重循环完成,外层循环执行 n-1 次,内层循环执行次数取决于第 i 个记录前有多少个记录的关键码大于第 i 个记录的关键码。
3. 例子【考点】
【考点】:给出一个序列,问排序要进行多少步?每一步要怎么写?
4.代码实现
(1)无哨兵版
注意:
①temp每次与有序表中的元素 j 比较后,若temp大于元素 j ,需要将元素 j 后移;后移的方法是将 元素 j 的值赋值为 元素j+1;同时 j- -,temp继续向前比较。当temp>元素 j 时,循环停止,这时候应该将temp插入到元素 j 的后面,即data[ j+1 ]=temp。
②同时寻找插入位置时,除了比较条件,还有数组的索引不可越界,即 j>=0
void SinsertSort(SStable ST)
{
int i, j;
for (i = 1; i <= ST.length; i++)//0位置是哨兵,1位视为已排好序
{
if (ST.data[i] < ST.data[i - 1])//小于前驱 //也可以不需要这个if判断
{ //找到合适的位置,插入有序表
int temp = ST.data[i]; //暂存待插入的元素
int j = i - 1;
while (temp < ST.data[j] && j >= 0)
{//寻找插入位置,temp与前面排好序的元素对比
ST.data[j + 1] = ST.data[j]; //记录后移动,让前面给temp腾位置
j--;
}
ST.data[j + 1] = temp;//将temp值复制到插入位置
//另一种循环:for(j=i-1;temp<ST.data[j] && j>=1;--j)
}
}
}
(2)有哨兵版
- 不同之处在于,不需要每次循环都判断 j>=1。因为当 j=0时,循环进行的条件不满足,循环自动停止。
- 并且外层循环从i=2开始,每次将待排序的元素赋值给哨兵(st.data[0])
void SinsertSort2(SStable st)
{
int i, j;
for (i = 2; i <= st.length; i++)
{
if (st.data[i] < st.data[i - 1])
{
st.data[0] = st.data[i]; //0处复制为哨兵
for (j = i - 1; st.data[0] < st.data[j]; j--)
{
st.data[j + 1] = st.data[j];
}
st.data[j + 1] = st.data[0];
}
}
}
5.性能分析
(1)空间复杂度:
只需要一个记录的辅助空间,用于暂存待插入记录的暂存单元。 仅使用了常数个辅助单元,因而空间复杂度为O(1)
(2)时间复杂度
最好的情况:序列为正序,进行n-1趟操作;每一趟只需进行1次比较(和直接前驱比较即可),时间复杂度为O(n);
最差的情况:序列为逆序,进行n-1趟操作;每一趟不仅要进行比较(最后一个要比较n-1次),还要移动,时间复杂度为 【比较和移动是数量级都为n 】
6.链表实现
移动元素的次数变少,但是关键字对比的次数依然是O(n^2)数量级,整体来看时间复杂度还是O(n^2 )
自己思考:①直接插入用什么数据结构?因为要一直插入,所以用链表合适 ②需要确保前面那段是顺序的(即括号内),要找到合适的位置放进去;如何加速呢?用折半查找,但折半查找仅限域顺序表。
(二)折半插入排序
(具体过程见王道视频)
1. 排序过程:
用折半查找方法确定插入位置、再移动元素的排序
2. 排序思想
当low>high时折半查找停止,应将[ low, i-1 ]内的元素全部右移(即 high+1 右边的元素全部右移,因为high右边的都是大于目标的),并将A[0]赋值到low所指位置。其中需要注意:
- 当st.data[mid]=st.data[0]时,应继续在mid所指位置右边寻找插入位置。(有两种可能:mid==0或中间序列有相等的值,但不管是哪种,都应这样做)
- 当st.data[ i ]比有序表中的数字都大时,最后low = i,不需要移动记录。
3.代码实现
初版:
//折半插入1
void half_sort(SStable st)
{
int i;
for (i = 2; i <= st.length; i++)//进行n-1次循环
{
st.data[0] = st.data[i];//设置哨兵,暂存目标
//查找插入位置
int low = 1;
int high = i - 1;
int mid = (low + high) / 2;
while (low <= high)
{
if (st.data[mid] < st.data[0])
{
low = mid + 1;
}
else if (st.data[mid] > st.data[i])
{
high = mid - 1;
}
else//st.data[mid]=st.data[0]
{//要么mid=0,要么在有序表中间相等
low = mid + 1;//继续往mid右边寻找
}
mid = (low + high) / 2;
}//low>high时查找停止,[low,i-1]的元素全部右移,DATA[0]赋值到low
if (low == i)
;
else
{
for (int j = i - 1; j >= low; --j)
{
st.data[j + 1] = st.data[j];
}
st.data[low] = st.data[0];
}
}
}
改进:
- 折半查找中,st.data[mid] < st.data[0]和st.data[mid]=st.data[0]两种情况的处理方式可以合并,都是low = mid + 1
- 折半查找后的移动过程中,不需要再判断low == i。因为当low=i,j=i-1,不满足for循环进行的条件j >= low,循环不会进行,也就不会移动记录
void half_sort2(SStable st)
{
int i;
for (i = 2; i <= st.length; i++)//进行n-1次循环
{
st.data[0] = st.data[i];//设置哨兵,暂存目标
//查找插入位置
int low = 1;
int high = i - 1;
int mid = (low + high) / 2;
while (low <= high)
{
if (st.data[mid] > st.data[i])
{
high = mid - 1;//查找左半子表
}
else
{
low = mid + 1;//查找右半子表 !!与折半查找的不同之处
}
mid = (low + high);
}//low>high时查找停止,[low,i-1]的元素全部右移,DATA[0]赋值到low
for (int j = i - 1; j >= low; --j)
{
st.data[j + 1] = st.data[j];//统一后移元素,空出插入位置
}
st.data[low] = st.data[0];//插入操作
}
}
4. 性能分析
从上述算法中,不难看出折半插入排序仅减少了比较元素的次数,约为 ,该比较次数与待排序表的初始状态无关,仅取决于表中的元素个数 n;而元素的移动次数并未改变(数量级依然是n),它依赖于待排序表的初始状态。因此,折半插入排序的时间复杂度仍为O(n^2 )
(三)希尔排序(缩小增量法)
8.2_2_希尔排序_哔哩哔哩_bilibili (具体过程见视频!!)
1. 排序过程:
- 先追求表中元素部分有序,再逐渐逼近全局有序!!
先取一个正整数d1<n,把所有相隔d1( i+di,i+2di,i+3di.....)的记录放一组,对各个组分别进行直接插入排序;然后取d2<d1,重复上述分组和排序操作;直到di=1,即所有记录放进一个组中排序为止
2.例子【考点】
当d=5,3,1时,排序序列分别怎么写?
3. 代码实现
代码实现的过程与手算实现的过程不同。关键点:
1.插入排序时,如何遍历顺序表?,从i=d+1往后开始遍历,每次i++,直到结束,即可遍历完每一组除了第一个的所有元素。
2.同一组内如何排序?比较当前元素st.data[i]和间隔为d的前一个元素st.data[i-d];如果当前元素小于间隔为d的前一个元素,则需要进行插入排序,将st.data[i]暂存在st.data[0],向前(初始j=i-d;每次:j=j-d)查找合适的位置插入。
void shell_sort(SStable st)
{
int i, j;
for (int d = st.length / 2; d >= 1; d = d / 2)
{
//一个个遍历顺序表d+1、d+2、d+3的元素,直接插入排序
for (i = d + 1; i <= st.length; i++)//从d+1开始,因为每个子表的第一个视为顺序的
{
//组内直接插入排序
if (st.data[i] < st.data[i - d])
{
st.data[0] = st.data[i];//暂存
for (j = i - d; j > 0 && st.data[0] < st.data[j]; j = j - d)
{ //循环条件的j>0可去掉
st.data[j + d] = st.data[j];
}
st.data[j + d] = st.data[0];
}
}
}
}
/*特定步长设置*/
int gap[4]={5,3,1};
for(i=0;i<3;i++)
{
int thea=gap[i];
........
}
4.性能分析
极大减小了移动次数,但是比较次数没有减小。
三、交换排序
1. 冒泡排序
2. 快速排序
考点:给出快排的每趟结果;填空:i=j,交换,从下一个开始与分快查找结合