【数据结构】插入排序、详细解释希尔排序、堆排序、选择排序

一、直接插入排序

将一个数组进行直接插入排序,每次取数组中一个数A保存起来,和此数下标之前已经排好的数组进行比较(第一次因为前面没有数,直接取数组第二个数),假设需要升序数列,如果A数小于要比较的数,就继续往数组中更小下标的数比较(在这个过程中因为A数已经保存起来了可以一边比较一边将比较过的数往后移一位,因为A迟早要插入到序列中会占一位),直到找到不小于A的数,就将A插入当前下标位置,继续下一个数的插入。图解如下:
在这里插入图片描述
代码实现如下:

void InsertSort(type array[],int size)
{
type ret; 
int i,j;
assert(array);
for(i=0 ;i<size-1 ;i++)
{
 ret = array[i+1];
 j = i;
 while(ret < array[j] && j >= 0)    //如果大于ret就继续找比ret小的,找到或者到数组边界就停止
 {
  array[j+1] = array[j];         //后移每个比ret大的数
  j--;
 }
 array[j+1] = ret;
}
}
  • 元素集合越接近有序,直接插入排序算法的时间效率越高
  • 最优情况下:时间效率为O(n)
  • 最差情况下:时间复杂度为O( )
  • 空间复杂度:O(1)
  • 它是一种稳定的排序算法

二、希尔排序(插入排序的优化)

(1)希尔排序(shell sort)这个排序方法又称为缩小增量排序,是1959年D·L·Shell提出来的。该方法的基本思想是:设待排序元素序列有n个元素,首先取一个整数increment(小于n)作为间隔将全部元素分为increment个子序列,所有距离为increment的元素放在同一个子序列中,在每一个子序列中分别实行直接插入排序。然后缩小间隔increment,重复上述子序列划分和排序工作。直到最后取increment=1,将所有元素放在同一个子序列中排序为止。
(2)由于开始时,increment的取值较大,每个子序列中的元素较少,排序速度较快,到排序后期increment取值逐渐变小,子序列中元素个数逐渐增多,但由于前面工作的基础,大多数元素已经基本有序,所以排序速度仍然很快。

  • 下面给出一个数据列:

在这里插入图片描述

  • 第一趟取increment的方法是:n/3向下取整+1=3(关于increment的取法之后会有介绍)。将整个数据列划分为间隔为3的3个子序列,然后对每一个子序列执行直接插入排序,相当于对整个序列执行了部分排序调整。图解如下:
    在这里插入图片描述

  • 第二趟将间隔increment= increment/3向下取整+1=2,将整个元素序列划分为2个间隔为2的子序列,分别进行排序。图解如下:
    在这里插入图片描述

  • 第3趟把间隔缩小为increment= increment/3向下取整+1=1,当增量为1的时候,实际上就是把整个数列作为一个子序列进行插入排序,图解如下:
    在这里插入图片描述

  • 直到increment=1时,就是对整个数列做最后一次调整,因为前面的序列调整已经使得整个序列部分有序,所以最后一次调整也变得十分轻松,这也是希尔排序性能优越的体现。

  • 代码实现如下:

void ShellSort(type array[],int size)
{
 type ret; 
 int i,j;
 int n = 3;            //直接将增量设置为3了
 assert(array);
 while(n)
 {
  for(i=0 ;i<size-n ;i++)
  {
   ret = array[i+n];
   j = i;
   while(ret < array[j] && j >= 0)
   {
    array[j+n] = array[j];
    j -= n;
   }
   array[j+n] = ret;
  }
  n--;
 }
}

关于希尔排序increment(增量)的取法
增量increment的取法有各种方案。最初shell提出取increment=n/2向下取整,increment=increment/2向下取整,直到increment=1。但由于直到最后一步,在奇数位置的元素才会与偶数位置的元素进行比较,这样使用这个序列的效率会很低。后来Knuth提出取increment=n/3向下取整+1.还有人提出都取奇数为好,也有人提出increment互质为好。应用不同的序列会使希尔排序算法的性能有很大的差异。

shell排序的时间复杂度是根据选中的增量d 有关的,所以分析shell排序的时间复杂度是个比较麻烦的事;这里只给出答案,不推算了;

  • 在最优的情况下,时间复杂度为:O(n ^ (1.3) ) (元素已经排序好顺序)
  • 在最差的情况下,时间复杂度为:O(n ^ 2);
  • 希尔排序是一个不稳定的算法

三、选择排序

每一趟(第i趟,i=0,1,…,n-2)在后面n-i个待排序的数据元素集合中选 出关键码最小的数据元素,作为有序元素序列的第i个元素。待到第n-2趟 做完,待排序元素集合中只剩下1个元素,排序结束。图解:
在这里插入图片描述
代码实现:

void SelectSort(type array[],int size)
{
int i,j;
type min;
for(i=0 ;i<size-1 ;i++)
{
 min = array[i];
 for(j=i+1 ;j<size ;j++)
 {
  if(array[j] < min)
   Swap(&array[j],&min);      //交换两个数的函数
 }
 array[i] = min;
}
}
  • 直接选择排序的时间复杂度为O( );
  • 它是一种不稳定的排序算法

直接选择排序的优化

既然每次遍历都找数组中最小的数放到第i位,为何不设置两个下标位begin和end,分别控制数组的首位和末尾,每一次变量都能找到一个最小的和一个最大的,分别begin和end的位置。begin向后移动,end向前移动。
代码实现如下:

void SelectSort_OR(type array[],int size)
{
int start = 0,end = size-1;
int j;
type min;
type max;
for(start=0 ;start <= end ;start++)
{
 min = array[end];
 max = array[start];
 for(j=start ;j<end ;j++)
 {
  if(array[j]<min)
   Swap(&array[j],&min);
  if(array[j]>max)
   Swap(&array[j],&max);
 }
 array[end] = min;
 array[start] = max;
}
}

4.堆排序

大堆的最顶端是数组中最大的数,小堆中最顶端是数组中最小的数。而最顶端是数组下标为0的位置,
以大堆为例,如果堆已经创建成功,每次让堆的数量减一,这个操作会让堆顶的数和堆尾的数互换,然后堆数量size减一,这样交换给堆尾的那个最大的数会保护起来。然后对堆进行向下调整,继续重复上述操作。代码如下:


void RemoveHeap(pHeapStack pHs)
{
 assert(pHs);
 Swap(&pHs->data[0],&pHs->data[pHs->sz-1]);
 pHs->sz--;
 AdjustDown(pHs,0);
}

void HeapSort(pHeapStack pHs)
{
 while(pHs->sz)
 {
  RemoveHeap(pHs);		//虽然pHs->sz为0了,但是堆数组是有序的
 }
}
  • 把一棵完全二叉树调整为堆,以及每次将堆顶元素交换后 进行调整的时间复杂度均为O( logn),所以堆排序的时 间复杂度为:O(nlogn )
  • 堆排序是一种不稳定的排序算法

猜你喜欢

转载自blog.csdn.net/qq_39487033/article/details/83244048