数据结构_排序

1、排序的基本概念和分类

在这里插入图片描述
注意在排序问题中,通常将数据元素称为记录。显然输入的是一个记录集合,输出的也是一个记录集合,所以,可以将排序看成是线性表的一种操作。
排序的依据是关键字之间的大小关系,那么,对于同一个记录集合,针对不同的关键字进行排序,可以得到不同的序列。
在这里插入图片描述
在这里插入图片描述
从上面的例可以看出,多个关键字的排序最终可以转换成单个关键字的排序,故这里主要讨论单个关键字的排序。

(1)排序的稳定性

也正是由于排序不仅是针对主关键字,那么对于次关键字,因为待排序的记录序列中可能存在两个或两个以上的关键字相等的记录,排序结果可能会存在不唯一的情况,由此给出了稳定与不稳定排序的定义。
在这里插入图片描述

(2)内排序与外排序

根据在排序过程中待排序的记录是否被全部放置在内存中,排序分为:内排序和外排序。
内排序是在排序的整个过程中,待排序的所有记录全部放置在内存中。外排序是由于排序的记录个数太多,不能同时放置在内存,整个排序过程需要在内外存之间多次交换数据才能进行。这里主要介绍内排序的多种方法。
对于内排序来说,排序性能主要受以下三个方面影响。
1)时间性能
排序是数据处理中经常执行的一种操作,往往属于系统的核心部分,因此排序算法的时间开销是衡量其好坏的最重要的标志。在内排序中主要进行两种操作:比较和移动。比较指关键字之间的比较,这是要做排序最起码的操作。移动指记录从一个位置移动到另一个位置,事实上,移动可以通过改变记录的存储方式来予以避免(后面在讲解具体的算法时会提及)。总之,高效率的排序算法应该具有尽可能少的关键字比较次数和尽可能少的记录移动次数。
2)辅助空间
评价排序算法的另一主要指标是执行算法所需要的辅助存储空间。辅助存储空间是指除了存放待排序所占用的存储空间外,执行算法所需要的其他存储空间。
3)算法的复杂性
注意这里指的是算法本身的复杂度,而不是指算法的时间复杂度。显然算法过于复杂也会影响排序的性能。

根据排序过程中借助的主要操作,把内排序分为插入排序交换排序选择排序归并排序
在这里插入图片描述

(3)排序用到的结构与函数

SqList为排序用的顺序表结构,此结构也将用于后续所有排序算法。

#define MAXSIZE 10
typedef struct
{
 int r[MAXSIZE + 1];/*r[0]用于作哨兵或或临时变量*/
 int Length;/*用于记录顺序表的长度*/
}SqList;

另外,由于排序最常用到的操作是数组两元素的交换,故将它写成函数。

/*交换数组L中下标为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;
}

2、冒泡排序

(1)最简单排序实现

冒泡排序(Bubble Sort)一种交换排序,它的基本思想是:两两比较相邻记录的关键字,如果反序则交换,直到没有反序的记录为止。冒泡的实现在细节上可以有很多种变化,这里将介绍三种不同的冒泡实现代码。

/*对顺序表L作交换排序(冒泡排序初级版)*/
void BubbleSort0(SqList* L)
{
 int i, j;
 for (i = 1; i < L->Length; i++)
 {
  for (j = i + 1; j < L->Length; j++)
  {/*该循环结束后L->r[i]为最小值*/
   if (L->r[i]>L->r[j])
    swap(L,i,j);/*交换L->r[i]与L->r[j]的值*/
  }
 }
}

在这里插入图片描述
在这里插入图片描述

(2)冒泡排序算法

正宗的冒泡排序算法:

/*顺序表L作冒泡排序*/
void BubbleSort(SqList* L)
{
 int i, j;
 for (i = 1; i < L->Length; i++)
 {
  for (j = L->Length - 1; j >= 1; j--)/*注意j是从后往前循环*/
  {
   if (L->r[j] > L->r[j + 1])/*若前者大于后者(注意与上一算法的差异)*/
    swap(L, j, j + 1);/*交换L->r[j]与L->r[j+1]*/
  }
 }
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

(

(3)冒泡排序优化

在这里插入图片描述
在这里插入图片描述

/*顺序表L作改进冒泡排序*/
void BubbleSort2(SqList* L)
{
 int i, j;
 Status flag = TRUE;
 for (i = 1; i < L->Length&&flag; i++)
 {
  flag = FALSE;
  for (j = L->Length - 1; j >= i; j--)/*注意j是从后往前循环*/
  {
   if (L->r[j] > L->r[j + 1])/*若前者大于后者(注意与上一算法的差异)*/
   {
    swap(L, j, j + 1);/*交换L->r[j]与L->r[j+1]*/
    flag = TRUE;
   }
  }
 }
}

在这里插入图片描述

(4)冒泡排序复杂度分析

在这里插入图片描述

3、简单选择排序

冒泡排序的思想就是在不断地在交换,通过交换完成最终的排序。为了减少交换次数,可以在排序时找到合适的关键字再做交换,并且只移动一次就完成相应关键字的排序定位工作。这就是选择排序的初步思想。
选择排序的基本思想是每一趟在n-i+1个(i=1,2,……,n-1)记录中选取关键字最小的记录作为有序序列的第i个记录。

(1)简单选择排序算法

简单选择排序算法(Simple Selection sort)就是通过n-i次关键词间的比较,从n-i+1个记录中选出关键字最小的记录,并和第i(1≤i≤n)个记录交换之。
以下是该算法代码:

/*对顺序表L作简单选择排序*/
void SelectSort(SqList* L)
{
 int i, j, min;
 for (i = 1; i < L->Length; i++)
 {
  min = i;/*将当前下标定义为最小值下标*/
  for (j = i + 1; j < L->Length; j++)/*循环之后的数据*/
  {
   if (L->r[min] > L->r[j])/*如果有小于当前最小值的关键字*/
    min = j;            /*将此关键字的下标赋值给min*/
  }
  if (min != i)               /*如果min不等于i,说民找到最小值,交换*/
   swap(L, i, min);        /*交换L->r[i]与L->r[min]的值*/
 }
}

在这里插入图片描述
在这里插入图片描述

(2)简单选择排序复杂度分析

在这里插入图片描述在这里插入图片描述

4、直接插入排序

在这里插入图片描述

(1)直接插入排序算法

直接插入排序(Straight Insertion Sort)的基本操作是将一个记录插入到已经排好序的有序表中,从而获得一个新的、记录新增1的有序表。

/*对顺序表L作直接插入排序*/
void InsertSort(SqList* L)
{
 int i, j;
 for (i = 2; i < L->Length; i++)
 {
  if (L->r[i] < L->r[i - 1])/*需将L->r[i]插入有序子表*/
  {                         /*即L->r[i]需要往前移*/
   L->r[0] = L->r[i];/*设置哨兵*/
   for (j = i - 1; L->r[j] > L->r[0]; j--)
    L->r[j+1] = L->r[j];/*记录后移*/
         /*L->r[i]往前移动后,其移动到的位置之后的*/
         /*元素到位置i-1的元素均向后移动一位,类似于顺序表的插入*/
   L->r[j + 1] = L->r[0];  /*插入到正确位置*/
  }
 }
}
/*L->r[i]往前移动后,其移动到的位置之后的元素到位置*/
/*i-1的元素均向后移动一位,类似于顺序表的插入*/

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

(2)直接插入排序复杂度分析

在这里插入图片描述
在这里插入图片描述

5、希尔排序

优秀排序算法的首要条件就是速度,前面提到的三种不同排序算法,其时间复杂度都是O(n²),下面介绍的算法的时间复杂度突破了O(n²)的限制。

(1)希尔排序原理

在这里插入图片描述
在这里插入图片描述

(2)希尔排序算法

希尔排序算法代码如下:

/*对顺序表L作希尔排序(与直接插入排序相比,将1改为增量imcrement)*/
void ShellSort(SqList* L)
{
 int i, j;
 int imcrement = L->Length;
 do
 {
  imcrement = imcrement / 3 + 1;/*增量序列*/
  for (i = imcrement + 1; i < L->Length; i++)
  {
   if (L->r[i] < L->r[i - imcrement])
   {/*需将L->r[i]插入有序增量表*/
    L->r[0] = L->r[i];/*暂存在L->r[0]*/
    for (j = i - imcrement; j > 0 && L->r[0] < L->r[j]; j -= imcrement)
     L->r[j + imcrement] = L->r[j];/*记录后移,查找插入位置*/
    L->r[j + imcrement] = L->r[0];/*插入*/
   }
  }
 } while (imcrement > 1);
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

(3)希尔排序复杂度分析

通过上段代码的分析,可以发现,希尔排序的关键并不是随便分组后各自排序,而是将相隔某个"增量"的记录组成一个子序列,实现跳跃式的移动,使得排序的效率提高。
在这里插入图片描述
在这里插入图片描述

6、堆排序

在这里插入图片描述
如果可以做到每次在选择到最小记录的同时,并根据比较结果对其他记录做出相应的调整,那么排序的整体效率就会非常高了。而堆排序(Heap Sort)就是对简单选择排序进行的一种改进,这种改进的效果是非常明显的。
在这里插入图片描述
在这里插入图片描述
堆是有下列性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子的值,称为小顶堆
在这里插入图片描述
在这里插入图片描述

(1)堆排序算法

堆排序(Heap Sort)就是利用堆(假设利用大顶堆)进行排序的方法。它的基本思想是,将待排序的序列构造成一个大顶堆。此时,整个序列的最大值就是堆顶的根结点。将它移走(其实就是将它与堆数组的末尾元素交换,此时末尾元素就是最大值),然后将剩余的n-1个序列重新构造成一个堆,这样就会得到n个元素中的次小值。如此反复执行,便能得到一个有序序列了。
在这里插入图片描述
在这里插入图片描述

/*对顺序表L进行堆排序*/
void HeapSort(SqList* L)
{
 int i;
 for (i = L->Length / 2; i > 0; i--)/*把L中的r构造成一个大顶堆*/
  HeapAdjust(L,i,L->Length);
 for (i = L->Length; i > 1; i--)
 {
  swap(L, i, 1);/*将堆顶记录和当前未经排序子序列的最后一个记录变换*/
  HeapAdjust(L,1,i-1);/*将L->r[1..i-1]重新调整为大顶堆。因为第i个元素被拿掉,所以是i-1*/
 }
}

在这里插入图片描述
在这里插入图片描述

/*已知L->r[s..m]中记录的关键字除L->[s]之外均满足堆定义*/
/*本函数调整L->r[s]的关键字,使L->r[s..m]满足大顶堆*/
void HeapAdjust(SqList* L,int s,int m)
{
 int temp, j;
 temp = L->r[s];
 for (j = 2 * s; j <= m; j *= 2)/*沿关键字较大的孩子结点向下筛选*/
 {
  if (j < m&&L->r[j] < L->r[j + 1])/*j为关键字中较大记录的下标*/
   ++j;
  if (temp >= L->r[j])
   break;
  L->r[s] = L->r[j];/*j应该插入在位置s上*/
  s = j;
 }
 L->r[s] = temp;/*插入。即L->r[s]和L->r[j]交换*/
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

(2)堆排序复杂度分析

在这里插入图片描述在这里插入图片描述

8、归并排序

在这里插入图片描述
在这里插入图片描述

(1)归并排序算法

“归并"一词的中文含义就是合并、并入的意思,而在数据结构中的定义是将两个或两个以上的有序表组合成一个新的有序表。
归并排序(Merging Sort)就是利用归并的思想实现排序的方法。它的原理是假设初始序列含有n个记录,则可以看成是有序的n个子序列,每个子序列的长度为1,然后两两归并,得到[n/2] ( [x]表示不小于x的最小整数)个长度为2或1的有序子序列;再两两归并,……,如此重复,直至得到一个长度为n的有序序列为止,这种排序方法称为2路归并排序
代码如下:

/*对顺序表L作归并排序*/
void MergeSort(SqList* L)
{
 MSort(L->r,L->r,1,L->Length);
}

在这里插入图片描述

/*将SR[s..t]归并排序为TR1[s..t]*/
void MSort(int SR[], int TR1[], int s, int t)
{
 int m;
 int TR2[MAXSIZE + 1];
 if (s == t)
  TR1[s] = SR[s];
 else
 {
  m = (s + t) / 2;/*将SR[s..t评分成SR[s..m]和SR[m+1,t]*/
  MSort(SR,TR2,s,m);/*递归将SR[s..m]归并为有序TR2[s..m]*/
  MSort(SR, TR2, m+1, t);/*递归将SR[m+1..t]归并为有序TR2[m+1..t]*/
  Merge(TR2, TR1, s, m,t);/*将TR2[s..m]和TR2[m+1..t]归并到TR1[s..t]*/
 }
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
Merge函数的代码:

/*将有序的SR[i..m]和SR[m+1,n]归并为有序的TR[i..n]*/
void Merge(int SR[], int TR[], int i, int m, int n)
{
 int j, k, l;
 for (j = m + 1, k = i; i <= m && j <= n; k++)/*将SR中记录由小到大归并入TR*/
 {
  if (SR[i] < SR[j])
   TR[k] = SR[i++];
  else
   TR[k] = SR[j++];
 }
 if (i <= m)/*这两个if语句只会执行一个*/
 {
  for (l = 0; l <= m - i; l++)
   TR[k + l] = SR[i + l];/*将剩余的SR[i..m]复制到TR*/
 }
 if (j <= n)
 {
  for (l = 0; l < n - j; l++)
   TR[k+l] = SR[j+l];/*将剩余的SR[j..n]复制到TR*/
 }
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

(2)归并排序复杂度分析

在这里插入图片描述

(3)非递归实现归并排序

在这里插入图片描述

/*对顺序表L作归并非递归排序*/
void MergeSort2(SqList* L)
{
 int* TR =(int*) malloc(L->Length * sizeof(int));
 int k = 1;
 while (k < L->Length)
 {
  MergePass(L->r,TR,k,L->Length);
  k =  2* k;/*子序列长度加倍*/
  MergePass(TR,L->r , k, L->Length);
  k = 2 * k;/*子序列长度加倍*/
 }
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

/*将SR[]中相邻长度为s的子序列两两归并到TR[]*/
void MergePass(int SR[],int TR[],int s,int n)
{
 int i = 1;
 int j;
 while (i <= n - 2 * s + 1)
 {
  Merge(SR,TR,i,i+s-1,i+2*s-1);/*两两归并*/
  i = i + 2 * s;
 }
 if (i < n - s + 1)/*归并最后两个序列*/
  Merge(SR, TR, i, i + s - 1, n);
 else /*若最后只剩下单个序列*/
  for (j = i; j <= n; j++)
   TR[j] = i;
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

9、快速排序

在这里插入图片描述
**希尔排序相当于直接插入排序的升级,它们同属于插入排序类,堆排序相当于简单选择排序的升级,它们同属于选择排序类。而快速排序其实就是我们前面认为最慢的冒泡排序的升级,它们同属于交换排序类。**即它是通过不断比较和移动交换来实现排序的,只不过它的实现,增大了记录的比较和移动的距离,将关键字较大的记录从前面直接移动到了后面,关键字较小的记录从后面直接移动到前面,从而减少了总的比较次数和移动交换次数。

(1)快速排序算法

快速排序(Quick Sort)的基本思想是:通过一趟排序将待排序记录分割成独立的两部分,其中一部分记录的关键字较比另一部分记录的关键字小,则可分别对这两部分记录继续进行排序,以达到整个记录有序的目的。
现假设要对数组{50,10,90,30,70,40,80,60,20}进行排序,代码如下:

/*对顺序表L作快速排序*/
void QuickSort(SqList* L)
{
 QSort(L,1,L->Length,);
}

又是一句代码,和归并排序一样,由于需要递归调用,因此我们在外封装了一个函数QSort()。

/*对顺序表L的子数列作顺序排序*/
void QSort(SqList* L,int low,int high)
{
 int pivot;
 if (low < high)
 {
  pivot = partition(L,low,high);/*将L->r[low..high]一分为二*/
                                /*算出枢轴值pivot*/
  QSort(L,low,pivot-1);         /*对低子表递归排序*/
  QSort(L, pivot+1,high);       /*对高子表递归排序*/
 }
}

在这里插入图片描述

/*此时在它之前(后)均不大(小)于它。*/
int Partition(SqList* L,int low,int high)
{
 int pivotkey;
 pivotkey = L->r[low];/*用子表的第一个记录作枢轴记录*/
 while (low < high)
 {
  while (low < high&&L->r[high] >= pivotkey)
   high--;
  swap(L, low, high);/*将比枢轴较小记录交换到低端*/
  while (low < high&&L->r[low] <= pivotkey)
   low++;
  swap(L,low,high);/*将比枢轴较大的记录交换到高端*/
 }
 return low;/*返回枢轴所在位置*/
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这里有一个问题,万一选取的partition恰好是数组中最小或最大值?

(2)快速排序复杂度分析

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

(3)快速排序优化

前面提到的排序算法有许多改进的地方,以下是一些优化方案:

  1. 优化选取中枢
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
 int pivotkey;
 int m = low + (high - low) / 2;/*计算数组中间的元素的下标*/
 if (L->r[low] > L->r[high])
  swap(L,low,high);/*交换左端与右端数据,保证左端数据较小*/
 if (L->r[m] > L->r[high])
  swap(L,high,m);/*交换中间与右端数据,保证中间数据较小*/
 if (L->r[low] > L->r[m])
  swap(L, low, m);/*交换左端与中间数据,保证左端数据较小*/
 pivotkey = L->r[low];/*用子表的第一个记录作枢轴记录*/

在这里插入图片描述
2)优化不必要的交换
在这里插入图片描述

/*快速排序算法优化*/
int Partition1(SqList* L,int low,int high)
{
 int pivotkey;
 int m = low + (high - low) / 2;/*计算数组中间的元素的下标*/
 if (L->r[low] > L->r[high])
  swap(L,low,high);/*交换左端与右端数据,保证左端数据较小*/
 if (L->r[m] > L->r[high])
  swap(L,high,m);/*交换中间与右端数据,保证中间数据较小*/
 if (L->r[low] > L->r[m])
  swap(L, low, m);/*交换左端与中间数据,保证左端数据较小*/
 pivotkey = L->r[low];/*用子表的第一个记录作枢轴记录*/
 **L->r[0] = pivotkey;/*将枢轴关键字备份到L->r[0]*/**
 while (low < high)
 {
  while (low < high&&L->r[high] >= pivotkey)
   high--;
  **L->r[low] = L->r[high];/*采用替换而非交换的方式进行操作*/**
  while (low < high&&L->r[low] <= pivotkey)
   low++;
  **L->r[high] = L->r[low];/*采用替换而非交换的方式进行操作*/**
 }
 **L->r[low] = L->r[0];/*将枢轴数据替换回L->r[0]*/**
 return low;/*返回枢轴所在位置*/
}

在这里插入图片描述
在这里插入图片描述
3)优化小数组时的排序方案
在这里插入图片描述

#define MAX_LENGTH_INSERT_SORT 7 /*数组长度阅值*/
/*对顺序表L的子数列L->r[low..high]作快速排序*/
void QSort(SqList* L,int low,int high)
{
 int pivot;
 **if ((high - low) > MAX_LENGTH_INSERT_SORT)**
 {/*当high-low大于常数时用快速排序*/
  pivot = Partition(L, low, high);/*将L->r[low..high]一分为二*/
           /*算出枢轴值pivot*/
  QSort(L, low, pivot - 1);         /*对低子表递归排序*/
  QSort(L, pivot + 1, high);       /*对高子表递归排序*/
 }
 else/*当high-low小于等于常数时用插入排序*/
  **InsertSort(L);**
}

在这里插入图片描述
4)优化递归操作
在这里插入图片描述
于是对QSort()实行尾递归优化,代码如下:

/*对顺序表L的子数列L->r[low..high]作快速排序*/
void QSort1(SqList* L, int low, int high)
{
 int pivot;
 if ((high - low) > MAX_LENGTH_INSERT_SORT)
 {/*当high-low大于常数时用快速排序*/
  **while (low < high)**
  {
   pivot = Partition(L, low, high);/*将L->r[low..high]一分为二*/
         /*算出枢轴值pivot*/
   QSort1(L, low, pivot - 1);         /*对低子表递归排序*/
   **low = pivot + 1;/*尾递归*/**
  }
 }
 else/*当high-low小于等于常数时用插入排序*/
  InsertSort(L);
}

在这里插入图片描述
5)了不起的排序算法
在这里插入图片描述
在这里插入图片描述

9、总结回顾
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值