数据结构与算法-排序
🌞入冬 时寒 添衣 勿病 要开心
排序
🎈1.排序的基本概念
排序是计算机程序设计中的重要操作,它将一组数据元素的任意序列,排列成按关键字有序的序列。其确切的定义如下:
设有n个数据元素的序列为{R1,R2,R3,...Rn}
,其关键字序列为{k1,k2,k3,...kn}
,排列就是确定1,2,3...n
的一种排列p1,p2,p3,...pn
,使表中元素相应的关键字满足非递减的关系kp1<=kp2<=kp3<=...kpn
,使得序列{R1,R2,...Rn}
成为一个按关键字有序的序列Rp1<=Rp2<=Rp3<=...Rpn
,这种操作称为排序。
如果待排序的表中,存在多个关键字相同的数据元素,经排序后这些相同的关键字的元素之间的相对次序保持不变,则称这种排序方法是稳定的。若具有相同关键字的数据元素之间的相对次序发生变化,则称这种排序方法是不稳定的。
🎈2.排序的分类
由于待排序数据元素的数量不同,使排序的过程涉及不同的存储器,可将排序分为内部排序和外部排序两大类。
内部排序:待排序数据元素全部存储在计算机内存中进行的排序过程。
外部排序:待排序的数据元素的数量较大,以致内存不能一次容纳全部数据元素,需要借助外存才能完成排序的过程。
🔎数据组织(采用顺序表的存储结构)
#define _CRT_SECURE_NO_WARNINGS 1
#define MaxSize 100
typedef int KeyType;//关键字类型定义为整型
typedef int InfoType;//
typedef struct
{
KeyType key;//关键字项
InfoType otherinfo;//其他数据项
}ElemType;
typedef struct
{
ElemType r[MaxSize];
int length;
}SqList;
🔭2.1插入排序
🔎2.1.1直接插入排序
假设待排元素存放在数组
r[1...n]
中,排序过程中的某一时刻,r
划分分成两个子区间r[1..i-1]
和r[i..n]
(起始时i=2
),其中前一个子区间r[1..i-1]
为已排好序的有序子表,后一个子区间r[i..n]
为当前为排好序的无序子表。直接插入排序的一趟操作是将当前无序子表的第一个元素r[i]
插入到有序子表r[1..i-1]
中适当位置上,使r[i..i]
变为新的有序子表。依次类推,直到全部元素插入完成为止。
void InsertSort(SqList& L)
{
int i, j;
for (i = 2; i <= L.length; i++)
{
L.r[0] = L.r[i];//复制哨兵
for (j = i - 1; L.r[0].key < L.r[j].key; j--)
{
L.r[j + 1] = L.r[j];//将关键字大于r[i].key的元素后移
}
L.r[j + 1] = L.r[0];//在j+1处插入元素r[i]
}
}
?例题:
🔎算法分析:
最好情况:正序时插入排序关键字比较次数为:n-1
最坏情况:反序时插入排序关键字的比较次数为:(n+2)(n-1)/2
空间复杂度:直接插入排序算法只使用两个变量i和j两个辅助变量,与问题的规模无关,所以直接插入排序的空间复杂度为O(1)
.
稳定性:直接插入排序是一种稳定的排序方法。
🔎2.1.2折半插入排序
🔎由于插入第i
个元素到r[1]
到r[i-1]
之间时,前i
个数据都是有序的,所以可以用折半查找的方法先在r[1]
到r[i-1]
中找到插入位置,再通过移动元素进行插入。具体流程如下:
- 在
r[low]
到r[high]
(初始时low=1,high=i-1
)中采用折半查找方法找到插入位置high+1
. - 将
r[high+1]
到r[i-1]
中元素后移一个位置. - 置
r[high+1]=r[i]
.
void BinsertSort(SqList& L)
{
int i, j, low, high, mid;
for (i = 2; i <= L.length; i++)
{
L.r[0] = L.r[i];
low = 1;
high = i - 1;
while (low <= high)
{
mid = (low + high) >> 1;
if (L.r[0].key < L.r[mid].key)
high = mid - 1;
else
low = mid + 1;
}
for (j = i - 1; j >= high + 1; j--)
{
L.r[j + 1] = L.r[j];
}
L.r[high + 1] = L.r[0];
}
}
🔎算法分析:
时间复杂度:n2
空间复杂度:O(1)
.
稳定性:折半插入排序是一种稳定的排序方法。
🔎2.1.3希尔排序
📖希尔排序又称缩小增量排序。其基本思路如下:
- 取定一个小于
n
(n
为表中元素的个数)的整数作为第一个增量d1
,把表的全部元素分成d1
个组,所有相互之间距离为d1
的元素放在同一个组中,各组内采用直接插入排序。 - 取第
2
个增量d2(d2<d1)
,重复上述分组和排序过程。 - 依次类推,直到增量
dt=1(dt<dt-1<...d2<d1)
,此时所有元素在同一组中采用直接插入排序。
注:希尔排序每趟排序不一定产生有序区,在最后排序结束前,希尔排序不能保证在每趟中将一个元素放到最终位置上。
void ShellInSort(SqList& L, int d)
{
int i, j;
for (i = d + 1; i <= L.length; i++)
{
if (L.r[i].key < L.r[i - d].key)
{
L.r[0] = L.r[i];
for (j = i - d; j > 0 && L.r[0].key < L.r[j].key; j -= d)
{
L.r[j + d] = L.r[j];
}
L.r[j + d] = L.r[0];
}
}
}
void ShellSort(SqList& L, int da[], int t)
{
int i;
for (i = 0; i < t; i++)
{
ShellInSort(L, da[i]);
}
}
🔎算法分析:
时间复杂度:n1.3
空间复杂度:O(1)
.
稳定性:希尔排序是一种不稳定的排序方法。
🔭2.2交换排序
交换排序是一类借助比较和交换来进行排序的方法,其基本思想是两两比较待排序数据元素的关键字,发现两个数据元素的次序相反时就进行交换,直到元素有序为止。交换排序主要有冒泡排序和快速排序。
🔎2.2.1冒泡排序
📝冒泡排序又称为起泡排序。其基本思想为:
对于n
个待排序的数据元素r[1...n]
,r[1]
和r[2]
比较,大者放在r[2]
中,r[2]
和r[3]
比较,大者放在r[3]
中,依次类推,r[n-1]
和r[n]
比较,大者放在r[n]
,这样经过一趟比较,r[1]
到r[n]
中的最大值放在r[n]
中;第二趟,对于n-1
个待排序数据元素r[1...n-1]
,采用上述方法进行比较,r[1]
到r[n-1]
的最大者放在r[n-1]
中;依次类推,最后一趟对于两个待排序元素r[1..2]
,r[1]
和r[2]
进行比较,大者放在r[2]
中。上述过程共进行n-1
趟完成冒泡排序。
void BubbleSort(SqList& L)
{
int i, j;
ElemType temp;
for (i = L.length; i > 1; i--)
{
for (j = 1; j < i; j++)
{
if (L.r[j].key > L.r[j + 1].key)
{
temp = L.r[j];
L.r[j] = L.r[j + 1];
L.r[j + 1] = temp;
}
}
}
}
🔎算法分析:
时间复杂度:n2
空间复杂度:O(1)
.
查找次数:n(n-1)/2
稳定性:冒泡排序是一种稳定的排序方法。
🔎2.2.2改进的冒泡排序
📖从上述算法可知,在某些情况下,第i(2<=i<=n)趟时数据元素已有序,但算法仍然执行后面几趟的比较。事实上,一旦算法中某一趟未出现任何元素交换,说明序列已有序,此时算法可结束。为此,改进的冒泡排序如下:
void BubbleSort(SqList& L)
{
int i, j;
int tag;
ElemType temp;
for (i = L.length; i > 1; i--)
{
tag = 0;
for (j = 1; j < i; j++)
{
if (L.r[j].key > L.r[j + 1].key)
{
temp = L.r[j];
L.r[j] = L.r[j + 1];
L.r[j + 1] = temp;
tag = 1;
}
}
if (!tag)//该趟没有数据元素的交换则退出循环
return;
}
}
?例题:
🔎算法分析:
时间复杂度:n
空间复杂度:O(1)
.
查找次数:n-1
稳定性:改进后的冒泡排序是一种稳定的排序方法。
🔎2.2.3快速排序
📖快速排序,又称分区交换排序,它是冒泡排序的一种改进排序方式。其基本思想是在待排序的n
个数据元素中以某个数据元素(通常为第一个元素)作为支点,将其放在适当的位置,数据元素序列被划分成两个部分,其中左半部分数据元素小于等于支点,右半部分数据元素大于等于支点,该过程称为一趟快速排序。依次类推,对左右两部分分别进行快速排序的递归处理,直至数据元素序列按关键字有序为止。快速排序的划分方法如下:设两个指针i和j
,开始分别指向表的开始与结束,任一时刻,支点保存于辅助变量temp
,即temp=L.r[i].
- 若
j>i&&L.r[j].key>=temp.key
,则两种位置合适,j
减1
,重复此过程;否则,两者位置不合适,L.r[i]=L.r[j].
- 若
i<j&&L.r[i].key<=temp.key
,则两者位置合适,i
加1
,重复该过程;否则,两者位置不合适,L.r[j]=L.r[i]
. - 重复上述过程,直至
i
与j
相等。 L.r[i]=temp
.
void QuickSort(SqList& L, int s, int t)
{
int i = s, j = t;
ElemType temp;
if (s < t)
{
temp = L.r[s];
while (i != j)
{
while (j > i && L.r[j].key >= temp.key)
j--;
L.r[i] = L.r[j];
while (i < j && L.r[i].key <= temp.key)
i++;
L.r[j] = L.r[i];
}
L.r[i] = temp;
QuickSort(L, s, i - 1);//左半区递归排序
QuickSort(L, i + 1, t);//右半区递归排序
}
}
🔎算法分析:
最好情况时间复杂度:nlog2n
最坏情况时间复杂度:n2,此时,比较次数为n(n-1)/2
空间复杂度:O(log2n)
.
稳定性:快速排序是一种不稳定的排序方法。
🔭2.3选择排序
选择排序的基本思想是每一趟(n个数据元素序列共进行n-1趟排序),从待排序的数据元素中选出最小(或最大)的数据元素,顺序放在已排好序的子表的最后,直至全部元素有序为止。选择排序适合于从大量元素中选择部分排序元素的情形。例如,从10000个数据元素中选择出关键字大小为前20位的数据元素。
🔎2.3.1简单选择排序
简单选择排序思想是第i(1<=i<=n-1)
趟排序开始时,从r[i..n]
中选出关键字最小(或最大)的数据元素r[k](i<=k<=n)
与r[i]
进行交换,经过n-1
趟排序后,表r[1..n]
递增(或递减)有序。
void SelectSort(SqList& L)
{
int i, j, k;
ElemType temp;
for (i = 1; i < L.length; i++)
{
k = i;
for (j = i + 1; j <= L.length; j++)
{
if (L.r[j].key < L.r[k].key)
k = j;
}
if (k != i)//交换r[k]和r[i]
{
temp = L.r[i];
L.r[i] = L.r[k];
L.r[k] = temp;
}
}
}
🔎算法分析:
比较次数:n(n-1)/2
时间复杂度:n2
空间复杂度:O(1)
.
稳定性:简单选择排序是一种不稳定的排序方法。
🔎2.3.2树形选择排序
树形选择排序,又称锦标赛排序,它是一种按照锦标赛的思路设计的选择排序方法,其每趟排序过程可用n个叶子结点的完全二叉树表示。每趟排序过程是:将
n
(n
一般为2
的幂)个数据元素的关键字作为叶子结点,相邻的两个叶子结点进行两两比较,然后在其中n/2
上取整个较小者(或较大者)之间再进行比较,重复该过程,直至选出最小(或最大)关键字的数据元素为止(此时完成一趟树形选择排序)。选出最小(后面以最小为例)关键字后,将叶子结点中的最小关键字改为“最大值”(如无穷),重复上述过程,直至所有元素有序为止。
🔎算法分析:
时间复杂度:O(nlog2n)
空间复杂度:O(n)
.
稳定性:树形选择排序是一种稳定的排序方法。
🔎2.3.3堆排序
堆排列是一种树形选择排序,主要利用堆的特性进行排序。它的特点是在排序过程中,将r[1…n]看成一棵完全二叉树的顺序存储结构,利用完全二叉树中双亲结点和孩子结点间的内在关系,在当前无序区中选择关键字最小(或最大)的数据元素。
?排序思想:堆排序的关键是用筛选法建立堆,首先对待排序的数据元素序列构造出一个堆,此时选出了堆中所有数据元素的最小者(以小顶堆为例)为堆顶,随后将它从堆中移走(通常是将堆顶数据元素和堆中最后一个数据元素进行交换),并将剩余的数据元素再调整成堆,这样又找出了次小的数据元素,依次类推,直到堆中只有一个数据元素为止。
void SiftAdjust(SqList L, int s, int t)
{
int i = s, j = 2 * i;
ElemType temp = L.r[i];
while (j <= t)
{
if (j<t && L.r[j].key>L.r[j + 1].key)
j++;
if (temp.key > L.r[j].key)
{
L.r[i] = L.r[j];
i = j;
j = 2 * i;
}
else break;
}
L.r[i] = temp;
}
void HeadSort(SqList& L)
{
int i;
ElemType temp;
for (i = L.length / 2; i >= 1; i--)
{
temp = L.r[1];
L.r[1] = L.r[i];
L.r[i] = temp;
SiftAdjust(L,1, i - 1);
}
}
🔎算法分析:
时间复杂度:O(nlog2n)
空间复杂度:O(1)
.
稳定性:堆排序是一种不稳定的排序方法。
🔭2.4归并排序
归并排序是多次将两个及两个以上的有序表合并成一个新的有序表的排序方法。最简单的归并排序是直接将两个有序的子表归并成一个有序表的2-路归并排序方法。
🔎算法分析:
时间复杂度:O(nlog2n)
空间复杂度:O(n)
.
稳定性:归并排序是一种稳定的排序方法。
🔭2.5基数排序
设有n个数据元素序列r[1…n],且每个关键字r[i].key由d位数字Kd-1Kd-2…K0组成(或每个数据元素r[i]中含有d个关键字),其中Kd-1为最高位,K0为最低位,每一位的值都在0<=Ki<r,其中r称为基数。基数排序分为如下两种情形:
🔎算法分析:
时间复杂度:O(d(n+r))(r为基数)
空间复杂度:O(r)
.
稳定性:基数排序是一种稳定的排序方法。
?总结:
好啦,关于内部排序的知识到这里就先结束啦,后期会继续更新学习数据结构与算法的相关知识,欢迎大家持续关注、点赞和评论!??????
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!