关于图的基础知识和图的存储相关实现,以及数据结构与算法的其他相关知识,请查看文章《数据结构与算法基础知识文章汇总》。
一、定义
假设含有N个记录的序列为(R1,R2,...,Rn),其相应的关键字为(K1,K2,...,Kn)。需确定1~N的一种排序P1,P2,...,Pn,使其相应的关键字满足Kp1<=Kp2<=...<=Kpn递减(递增)关系。即使得序列成为一个按关键字有序的序列(Rp1,Rp2,...,Rpn),这样的操作称为排序。
二、分类
在计算机算法中,排序分为以下两个分类:
- 1.
内排序,在排序的过程中,待排序的全部记录都被放置在内存中;- 2.
外排序,由于排序的记录太多,不能同时都放置在内存中,整个排序过程需要在内外存之间多次交换数据才能进行。
三、常用排序算法
1.在开始实现排序算法前,为方便算法的实现,设计如下数据结构和状态值
#define TRUE 1
#define FALSE 0
//用于要排序数组个数最大值,可根据需要修改
#define MAXSIZE 10000
typedef int Status;
typedef struct
{
//用于存储要排序数组,r[0]用作哨兵或临时变量
int r[MAXSIZE+1];
//用于记录顺序表的长度
int length;
}SqList;
2.实现打印函数
//3.数组打印
void print(SqList L)
{
int i;
for(i=1;i<L.length;i++)
printf("%d,",L.r[i]);
printf("%d",L.r[i]);
printf("\n");
}
3.实现交换算法
//2.排序常用交换函数实现
//交换L中数组r的下标为i和j的值
void swap(SqList *L,int i,int j)
{
int temp=L->r[i];
L->r[i]=L->r[j];
L->r[j]=temp;
}
1.冒泡排序
1.简单冒泡排序
void BubbleSort0(SqList *L){
int i,j;
for(i = 1; i < L->length; i++) {
for(j = i+1; j <= L->length; j++) {
if(L->r[i] > L->r[j])
swap(L, i, j);
}
}
}
- 1.
第0个位置是哨兵,所以从1开始;- 2.两个for循环的意思解释为:从
第1个元素开始,将表中的每个元素都与后面的元素进行比较,如果后面的元素较小,则交换两者,将小的数据放到前面,大的数据放到后面,最终得到一个升序的序列。
2.正宗冒泡排序
void BubbleSort(SqList *L){
int i,j;
for(i = 1; i < L->length; i++) {
//注意:j是从后面往前循环
for(j = L->length-1; j>=i; j--) {
//若前者大于后者(注意与上一个算法区别所在)
if(L->r[j]>L->r[j+1]){
//交换L->r[j]与L->r[j+1]的值;
swap(L, j, j+1);
}
}
}
}
- 1.内层循环是从数组的
倒数第二个元素开始的,因为需要将倒数第二和倒数第一进行比较;- 2.内层循环
第1次是倒数第二和倒数第一进行比较;第2次是倒数第三和倒数第二进行比较;第3次是倒数第四和倒数第三进行比较。每次比较都将较小的元素往前移;- 3.外层循环
第1次将最小的元素放在第1个位置;第2次将第二小的元素放在第2个位置,依此类推。
3.冒泡排序的优化
void BubbleSort2(SqList *L){
int i,j;
//flag用作标记
Status flag = TRUE;
//i从[1,L->length) 遍历;
//如果flag为False退出循环. 表示已经出现过一次j从L->Length-1 到 i的过程,都没有交换的状态;
for(i = 1; i < L->length && flag; i++) {
//flag 每次都初始化为FALSE
flag = FALSE;
for(j = L->length-1; j>=i; j--) {
if(L->r[j] > L->r[j+1]){
//交换L->r[j]和L->r[j+1]值;
swap(L, j, j+1);
//如果有任何数据的交换动作,则将flag改为true;
flag = TRUE;
}
}
}
}
- 1.
flag用来标记当后面的序列是否需要执行比较;- 2.如果内层循环中执行了
交换,说明后面的序列是无序的,下次还需要进行比较;- 3.
flag标记当前是否执行过交换,如果没有执行过交换,说明后面的序列都是有序的,也就无需再继续比较了。
如上图左边的序列,从第2个元素开始都是有序的,并且第一次执行,只有第1个和第2个执行了交换,此后的序列已经变成
有序的序列了,没有必要再继续循环比较下去了。
2.选择排序
void SelectSort(SqList *L){
int i,j,min;
for(i = 1; i < L->length; i++) {
//① 将当前下标假设为最小值的下标
min = i;
//② 循环比较i之后的所有数据
for(j = i+1; j <= L->length; j++) {
//③ 如果有小于当前最小值的关键字,将此关键字的下标赋值给min
if(L->r[min] > L->r[j]) {
min = j;
}
}
//④ 如果min不等于i,说明找到了最小值,则交换2个位置下的关键字
if(i!=min)
{
swap(L, i, min);
}
}
}
- 1.
选择排序就是选择出后面最小的元素与当前元素进行交换;- 2.从第1个开始,
依次和后面的元素进行比较,如果找到了比第1个元素小的,则进行交换。
3.插入排序
void InsertSort(SqList *L){
int i,j;
//L->r[0] 哨兵 可以把temp改为L->r[0]
int temp=0;
//假设排序的序列集是{0,5,4,3,6,2};
//i从2开始的意思是我们假设5已经放好了. 后面的牌(4,3,6,2)是插入到它的左侧或者右侧
for(i=2;i<=L->length;i++)
{
//需将L->r[i]插入有序子表
if(L->r[i]<L->r[i-1])
{
//设置哨兵 可以把temp改为L->r[0]
temp = L->r[i];
for(j=i-1;L->r[j]>temp;j--)
{
//记录后移
L->r[j+1]=L->r[j];
}
//插入到正确位置 可以把temp改为L->r[0]
L->r[j+1]=temp;
}
}
}
- 1.只有
一个元素时,默认是有序的,所以for循环从2开始;- 2.当比较到
第i个元素时,前i-1个元素一定是有序的;- 3.当
比较到第i个元素时,如果前面的i-1个元素有比它大的,则先记录i,再次前面的元素后移1位,直到不比i小时,停止后移。然后将i放置到空出来的位置。
2前面的序列都是
有序的,将2与前面序列的元素比较,如果比2大,则后移一位,直到出现比2小的元素或比较到第1元素为止。最后将2放置在前后移位后空置出来的位置上。
4.希尔排序
1.希尔排序实现思路
希尔排序就是将数组按下标的一定增量进行分组,再对每组使用插入排序算法进行排序。随着增量的减下,每组的元素越来越多,当增量减至为1时,刚好整个数组被分为一组。
例如:
2.代码实现
void shellSort(SqList *L){
int i,j;
int increment = L->length;
//0,9,1,5,8,3,7,4,6,2
//① 当increment 为1时,表示希尔排序结束
do{
//② 增量序列
increment = increment/3+1;
//③ i的待插入序列数据 [increment+1 , length]
for(i = increment+1; i <= L->length; i++) {
//④ 如果r[i] 小于它的序列组元素则进行插入排序,例如3和9. 3比9小,所以需要将3与9的位置交换
if(L->r[i] < L->r[i-increment]) {
//⑤ 将需要插入的L->r[i]暂时存储在L->r[0].和插入排序的temp 是一个概念;
L->r[0] = L->r[i];
//⑥ 记录后移
for(j = i-increment; j > 0 && L->r[0]<L->r[j]; j-=increment) {
L->r[j+increment] = L->r[j];
}
//⑦ 将L->r[0]插入到L->r[j+increment]的位置上;
L->r[j+increment] = L->r[0];
}
}
}while (increment > 1);
}
- 1.数据的长度为
length,将数据分为increment = length/3 + 1组,即间隔increment的下标元素为一组;- 2.对
间隔increment的下标的每组元素进行插入排序;- 3.
缩小分组,比如原来是4组,现在变成2组,再变成1组;依次对分为4组、2组和1组情况下的各小组进行插入排序;- 4.每次对分组进行插入排序时,都会把相对
较小的元素移动到前面去,经过几轮后,整个序列就变得相对有序了;- 5.所以在最后
increment = 1时,要再进行一次插入排序,保证最终得到的序列是绝对有序的。- 6.
希尔排序是对插入排序的升级,插入排序是从第2个元素开始比较并平移插入;而希尔排序是先将数组分成下标间隔为increment的increment等份,并分别对increment等份执行插入排序。通过缩小increment,达到数组的相对有序,以实现最终数组的绝对有序。相比希尔排序,插入排序的increment = 1。
5.堆排序
1.堆结构 堆是具有下面性质的完全二叉树:
- 1.每个结点的值都
大于等于左右孩子的值,称为大顶堆;- 2.每个结点的值都
小于等于左右孩子的值,称为小顶堆;
如果按照层序遍历的方式给结点从1开始编号,则结点之间满足如下关系:
2.大顶堆
- 1.
根结点60的编号是4,左孩子的编号是24=8,右孩子的编号是24+1=9,即30和20结点;- 2.
根结点60大于左孩子30,也大于右孩子20。
3.小顶堆
- 1.30结点的编号是4,左孩子的编号是24=8,右孩子的编号是24+1=9,即60和40结点;
- 2.30结点小于左孩子60,小于右孩子40。
也就是说,可以把数组看作是一个完成二叉树的堆结构。于是我们就可以利用堆结构的特征,来实现排序算法。
4.实现思路
- 1.根据需要构造大顶堆或小顶堆,如果是
升序,则构造大顶堆;如果是降序,则构造小顶堆;以升序构造大顶堆为例。- 2.将符合
完全二叉树的n/2个非叶子结点和它们的左右孩子调整为大顶堆,即找到它们的最大值放置到根结点,则得到根结点都大于左右孩子的结点的堆结构;- 3.交换
堆顶元素和末尾元素的值,原后再对交换后剩余元素构造大顶堆;
(1)例如对下面这棵完成二叉树的n/2个非叶子结点50、10、90、30构建大顶堆:
(2)构建的结果为:
(3)构建后进行交换堆顶与末尾元素:
最终,我们将最大的值90放在了堆顶位置。然后再将堆顶元素90和未尾的元素20进行交换,即把最大值放到了最后面去。
(4)交换堆顶和末尾元素后将剩余的元素再构建大顶堆:
交换后,
最大值放到了数组未尾。但前面n-1个元素又不符合大顶堆结构了,于是我们将前面n-1个元素再构建大顶堆。即找到n-1个元素中的最大值,然后再将这个最大值放到剩余元素的末尾。如此循环下来,我们将剩于元素的最大值都往后放了,直到全部放完为止,就得到了最终的有序序列。
5.代码实现
(1)构建大顶堆
void HeapAjust(SqList *L,int s,int m){
int temp,j;
//记录根结点
temp = L->r[s];
//找到左右子树,并与根结点进行比较后交换,s为根结点,则2 * s为左孩子,
for(j = 2 * s; j <=m; j*=2) {
//j为左孩子,j+1为右孩子。如果右孩子大于左孩子,则保存右孩子,否则j保存左孩子
if(j < m && L->r[j] < L->r[j+1])
{
++j;//j是左右孩子中的较大者
}
//比较根结点与j的大小
if(temp >= L->r[j])
{
break;
}
//j比根结点大,所以把j和根结点为进行交换
L->r[s] = L->r[j];
//记录被交换的结点
s = j;
}
//将要根结点的值赋给被交换的结点
L->r[s] = temp;
}
(2)堆排序实现
void HeapSort(SqList *L){
int i;
//将完全二叉树的n/2个非叶子结点构建大顶堆
for(i=L->length/2; i>0;i--){
HeapAjust(L, i, L->length);
}
//2. 将堆顶与末尾元素进行交换,再将剩余元素进行大顶堆的构建
for(i = L->length; i > 1; i--){
//① 将堆顶记录与当前未经排序子序列的最后一个记录进行交换;
swap(L, 1, i);
//② 将L->r[1...i-1]重新调整成大顶堆;
HeapAjust(L, 1, i-1);
}
}
四、总结
排序算法有:
- 1.
冒泡排序:从最后一个元素开始,依次与前一个元素进行比较,满足比较条件则交换的过程;- 2.
选择排序:从第i个元素开始,依次找到后面比自己更大(或更小)的元素,找到则交换的过程;- 3.
插入排序;从第1+i个元素开始,依次与前面的元素作比较,不满足比较,则将被比较的元素后移一位,直到满足比较时将当前元素插入空出来的位置;- 4.
希尔排序:将数组元素间隔划分为若干组,每组进行插入排序。缩小分组后,再对每组进行插入排序的过程;- 5.
堆排序:构建堆结构,将堆顶与末尾交换,再将剩余元素进行堆的构建,再交换堆顶与末尾元素的过程;