线性表的排序算法
排序相关的几个基本概念
- 排序依据是指数据元素的关键字,若关键字是主关键字,即关键字值不重复,则无论采用何种排序方法,排出的结果都是唯一的,若关键字是次关键字,即关键字值可以重复,则排出的结果可能不唯一,即相同关键字的数据在排序前后的相对位置发生了变化。
- 稳定排序:对于任意的数据元素序列,在排序前后相同关键字数据的相对位置都保持不变。
不稳定排序:存在一组数据序列,在排序前后,相同关键字数据的相对位置发生了变化。 - 算法效率评价:主要从两个方面进行考察:算法的空间复杂度、算法的时间复杂度。算法的执行时间一般分为最好情况下的时间复杂度、最差情况下的时间复杂度和平均情况下的时间复杂度。
- 排序方法:内部排序是指无须借助外存,直接在内存中完成的排序;外部排序是指数据量巨大,必须借助外存的帮助才能完成的排序。
直接插入排序(简单插入排序)
基本思想
将序列分成两部分,左边按关键字有序排列,右边无序排列。先将左边序列中第一个数据元素作为左边有序部分的第一个数据元素,然后将右边第一个记录插入到左边序列的适当位置,使得左边部分仍按关键字有序排列,重复下去,指导右边序列长度为0。
例:将[9,5,6,4,2]升序排序
9,5,6,4,2,1
5,9,6,4,2,1
5,6,9,4,2,1
4,5,6,9,2,1
2,4,5,6,9,1
1,2,4,5,6,9
代码示例
typedef int DataType;
typedef struct
{
DataType key;
float info;
}JD;
void Straisort(JD S[], int n)
{
int i, j;
JD media;
for (i = 0; i < n; i++)
{
media = S[i];
for (j = i - 1; media.key < S[j].key; j--)
{
S[j + 1] = S[j];
}
S[j + 1] = media;
}
}
性能分析
空间复杂度
直接插入排序在排序过程中,需要一个辅助空间S[0]。复杂度为O(1).
时间复杂度
- 最好情况。序列本身有序,每次排序过程只需1次比较,0次移动,整个排序过程共需要n-1次比较,0次数据移动。复杂度为O(n).
- 一般情况。第i趟排序操作需要和前面有序部分大约一半的记录进行比较,即大约比较 i 2 \frac{i}{2} 2i次,需要移动的数据量也大约为 i 2 \frac{i}{2} 2i次。时间复杂度为O(n2).
- 最坏情况。序列本身反序,因此
总的比较次数 = ∑ i = 2 n \sum_{i=2}^n ∑i=2n ( i - 1) = n ( n − 2 ) 2 \frac{n(n-2)}{2} 2n(n−2)
总的移动次数 = ∑ i = 2 n \sum_{i=2}^n ∑i=2n ( i - 1) = ( n + 2 ) ( n + 1 ) − 2 2 \frac{(n+2)(n+1)-2}{2} 2(n+2)(n+1)−2
由此可知,直接插入排序的时间复杂度在平均情况和最坏情况下都为O(n2).
简单选择排序
基本思想
从无序子序列中选择关键字最小(或最大)的记录,并将它加入到有序子序列中,以此增加记录的有序子序列的长度。
- 首先通过n-1次关键字比较,从n个记录中找出关键字最小的记录,将它与第一个记录交换
- 再通过n-2次比较,从剩余的n-1个记录中找出关键字次小的记录,将它与第二个记录交换
- 重复上述操作,共进行n-1趟排序后,排序结束。
例:对{49,38,65,97,76,13,27}进行升序排序
第一趟 13, {38,65,97,76,49,27}
第二趟 13,27, {65,97,76,49,38}
第三趟 13,27,38, {97,76,49,65}
第四趟 13,27,38,49, {76,97,65}
第五趟 13,27,38,49,65, {97,76}
第六趟 13,27,38,49,65,76, {97}
排序结束 13,27,38,49,65,76,97
代码示例
typedef int DataType;
typedef struct
{
DataType key;
float info;
}JD;
void Smp_Selesort(JD S[], int n)
{
int i, j, k;
JD media;
for (i = 0; i < n; i++)
{
k = i;
for (j = i + 1; j < n; j++)//找出关键字小的记录,然后进行交换
{
if (S[j].key < S[k].key)k = j;
}
if (i != k)
{
media = S[i];
S[i] = S[k];
S[k] = media;
}
}
}
性能分析
空间复杂度
简单选择排序算法需要一个额外的辅助存储单元,因此空间复杂度为O(1).
时间复杂度
- 最好的情况
第i趟选择需要进行比较n-i次,需要进行交换的次数为0,
总的比较次数 = ∑ i = 1 ( n − 1 ) \sum_{i=1}^{(n-1)} ∑i=1(n−1) ( n - i ) = n ( n − 1 ) 2 \frac{n(n-1)}{2} 2n(n−1)
总共需要的交换次数为0. - 最坏的情况
第i趟选择需要进行比较的次数为n-i,需要进行交换的次数为1,
总的比较次数 = ∑ i = 1 ( n − 1 ) \sum_{i=1}^{(n-1)} ∑i=1(n−1) ( n - i ) = n ( n − 1 ) 2 \frac{n(n-1)}{2} 2n(n−1)
总共需要的交换次数为n-1.
因此简单选择排序的时间复杂度为O(n2).
冒泡排序
基本思想
冒泡排序是一种交换排序,将元素进行两两比较和交换。
例:对序列{12,34,20,15,9}进行冒泡排序
第一趟
12,34,20,15,9
12,20,34,15,9
12,20,15,34,9
12,20,15,9,34 ←第一趟的结果
第二趟的结果
12,15,9,20,34
第三趟的结果
12,9,15,20,34
第四趟的结果
9,12,15,20,34
排序完成
代码示例(升序排序)
typedef int DataType;
typedef struct
{
DataType key;
float info;
}JD;
void Bubble_Sort(JD S[], int n)
{
int flag = 1;
JD media;
for (int i = 0; i < n - 1; ++i)
{
flag = 0;//用于判断是否发生交换
for (int j = 0; j < n - i - 1; ++j)
{
if (S[j].key > S[j + 1].key)
{
media = S[j];
S[j] = S[j + 1];
S[j + 1] = media;
flag = 1;
}
}
if (flag == 0)break;//此趟冒泡没有发生交换,排序结束
}
}
性能分析
空间复杂度
冒泡排序需要一个辅助存储单元,空间复杂度为O(1).
时间复杂度
- 最好的情况:序列本身就是排好序的,那么排序时一次交换都未发生,时间复杂度为O(n).
- 最坏的情况:序列本身是反序的,那么每趟冒泡都是必须的,共需要进行n-1趟冒泡,第i趟冒泡中比较的次数为n-i,数据交换n-i次
总的比较次数 = ∑ i = 1 ( n − 1 ) \sum_{i=1}^{(n-1)} ∑i=1(n−1) ( n - i ) = n ( n − 1 ) 2 \frac{n(n-1)}{2} 2n(n−1)
总的交换次数 = ∑ i = 1 ( n − 1 ) \sum_{i=1}^{(n-1)} ∑i=1(n−1) ( n - i ) = n ( n − 1 ) 2 \frac{n(n-1)}{2} 2n(n−1)
时间复杂度为O(n2).
冒泡算法的改进
对长度为n的序列S进行升序排序,若后面的一部分数已经是排好序的,则这部分数据就不需要再参与冒泡,从而一定程度上节省时间
代码示例
typedef int DataType;
typedef struct
{
DataType key;
float info;
}JD;
void Bubble_Modified_Sort(JD S[], int n)
{
int LastExchangeIndex;
JD media;
int i = n;
while(i>1)
{
LastExchangeIndex = 1;
for (int j = 0; j < i - 1; ++j)
{
if (S[j].key > S[j + 1].key)
{
media = S[j];
S[j] = S[j + 1];
S[j + 1] = media;
LastExchangeIndex = j + 1;//记录交换的位置
}
}
i = LastExchangeIndex;//本趟最后一次交换的位置
}
}