数据结构各内部排序算法总结对比及动图演示(插入排序、冒泡和快速排序、选择排序、堆排序、归并排序和基数排序等)1

简介: 数据结构各内部排序算法总结对比及动图演示(插入排序、冒泡和快速排序、选择排序、堆排序、归并排序和基数排序等)1


一、插入排序

插入排序包括直接插入排序,折半插入排序、希尔排序直接插入排序就是简单粗暴的插入,折半排序是利用了二分查找的插入排序,希尔排序是先局部后整体的插入排序。

其算法的主要思想就是每次将一个待排序的记录按其关键字大小插入到前面已经排好序的子序列,直到全部记录插入完成。

1.直接插入排序

①算法的执行过程:

对于待排序表L[1...n]假设在某个状态下,待排序元素为L(i),则L[1...i-1]为已经排好序的序列,L[i+1...n]为无序序列。

L(i)依次与L(i-1)...L(1)相比较,找出L(i)在有序序列中要插入的位置k

L[k...i-1]中的所有元素依次后移一个位置

L(i)复制到L(k)i后移,重复2.直到有序序列长度为n

②算法执行过程可视化演示:


③算法代码:

void InsertSort(ElemType A[], int n){
  for(int i = 2; i <= n; i++){        //默认首个元素为有序序列,将2~n位置的关键字依次插入 
    if(A[i] < A[i-1]){            //如果待插入元素小于有序序列最大元素,则需要插入 
      A[0] = A[i];            //A[0]为“哨兵”,用来存放待插入元素 
      for(int j = i-1; A[0] < A[j]; j--)  //从后往前查找待插入的位置 
        A[j+1] = A[j];          //依次向后移动 
      A[j+1] = A[0];            //找到位置之后,将待排序元素插入有序序列 
    }
  }
}

④性能分析:

1.空间效率:使用常数个辅助单元,空间复杂度为O(1)

2.时间效率:进行了n-1趟插入操作,每趟操作都分为比较和移动元素,所以与表的初始状态有关
最好情况下:表已经有序只需比较不需移动元素,此时时间复杂度为O(n);

最坏情况下:此时为逆序,比较次数为2+3+……+n,总的移动次数为(2+1)+(3+1)+……+(n+1)

平均情况下:出现概率随机取最好和最坏的平均值,总的比较和移动次数约为(1/4)n2.所以插入排序算法的时间复杂度为O(n2)

3.稳定性: 稳定

4.适用性:适用于线性表为顺序存储或链式存储的情况。

2.折半插入排序

①算法的执行过程: 总体过程与上一个类似,只是在寻找插入位置的时候使用的二分查找算法

②算法执行过程可视化演示: 与上一个相同。

③算法代码:

void BinaryInsertSort(ElemType A[], int n){
  int low, high, mid;
  for(int i = 2; i <= n; i++){      //默认首个元素为有序序列,将2~n位置的关键字依次插入
    A[0] = A[i];            //A[0]为“哨兵”,用来存放待插入元素
    low = 1, high = i-1;
    while(low <= high){         //low=high时表明查找到要插入的位置 
      mid = (low+high)/2;
      //为了保证稳定,相等的情况需要查找右半子表 
      if(A[mid] > A[0]) high = mid-1; //查找左半子表 
      else low = mid+1;       //查找右半子表 
    }
    for(int j = i-1; j >= high+1; j--)  //从后往前查找待插入的位置
      A[j+1] = A[j];          //依次向后移动
    A[high+1] = A[0];         //将待排序元素插入有序序列
  }
}

④性能分析:

1.空间效率:与直接插入排序相同空间复杂度为O(1)

2.时间效率:进行了n-1趟插入操作,每趟操作都分为比较和移动元素,比较操作与表的初始状态无关,为O(n log2n),移动次数取决于初始状态,所以折半插入排序时间复杂度为O(n2)
3.稳定性: 不稳定

4.适用性:仅适用于线性表为顺序存储的情况。

3.希尔排序

希尔排序的由来:当待排序序列为基本有序时,插入排序复杂度可以提高至O(n),所以我们可以让整体基本有序,也就是说部分有序,最后使用插入排序进行排序。由此得出希尔排序,也称缩小增量排序
①算法的执行过程:

希尔排序思想即先局部后整体,首先设置增量d,把待排序的表分为k个子表对每个子表排序后,增量d变为原来的一半直到减小为d=1.

此时的表已经基本有序,进行最后一趟排序之后得到有序序列,这里在每个子表中使用的排序算法仍是插入排序.

②算法执行过程演示:


③算法代码:

void ShellSort(ElemType A[], int n){
  //通过增量d把序列分为多个子表,外层for循环控制增量的变化 
  for(int dk = n/2; dk >= 1; dk /= 2){
    //对于每一个增量得到的子表进行插入排序 
     for(int i = dk+1; i <= n; i++){
      if(A[i] < A[i-dk]){     //如果待插入元素小于有序序列最大元素,则需要插入       
        A[0] = A[i];      //将元素暂存到A[0],但在这里并没有起到哨兵的作用 
        for(int j = i-dk; j > 0 && A[0] < A[j]; j -= dk)
          A[j+dk] = A[j];   //依次向后移动
        A[j+dk] = A[0];     //将待排序元素插入有序序列
      }
    }
  }
} 

④性能分析:

1.空间效率:使用常数个辅助单元,空间复杂度为O(1)

2.时间效率:由于希尔排序的复杂度依赖增量序列的函数,所以时间复杂度分析比较困难。当n在某个特定范围时,其时间复杂度为O(n1.3),最坏情况下为O(n2)。
3.稳定性:不稳定

4.适用性:仅适用于线性表为顺序存储的情况。


二、交换排序

此类排序是根据序列中两个元素关键字的比较结果来对换这两个记录在序列中的位置。

1.冒泡排序

①算法的执行过程:

冒泡排序是一种基于比较的简单交换排序,会进行多轮交换,在每趟排序中,从后往前两两比较相邻元素的值,若为逆序,则交换他们。
在每趟排序完成后,都会将当前待排序序列中最小的元素放到第一个位置(或最大的元素放到最后一个位置)。

最多进行n-1趟处理后,所有元素就能排好。第i趟排序要进行n-i次比较。

②算法执行过程可视化演示:


③算法代码:

void BubbleSort(ElemType A[], int n){
  for(int i = 0; i < n-1; i++){   //一共n-1趟
    for(int j = 0; j < n-1-i; j++){ //对应每一趟的比较
      if(A[j] > A[j+1]){      //若为逆序则交换
        swap(A[j], A[j+1]);
      }
    }
  } 
}

④性能分析:

1.空间效率:使用常数个辅助单元,空间复杂度为O(1)

2.时间效率:最好情况下时间复杂度为O(n),最坏情况下初始为逆序,需要n-1趟排序,第i趟需要n-i次关键字的比较,每次比较都需要移动元素3次来交换元素位置,所以最坏情况和平均情况复杂度都为O(n2).
3.稳定性:稳定

4.适用性:适用于线性表为顺序存储和链式存储的情况。

拓展(链式存储的冒泡排序):

void sort_list(PNODE pHead){
  int i,j,t;
  PNODE p, q;
  int len= length_list(pHead);
  for (i=0,p=pHead->pNext;i<len-1;++i,p=p->pNext){
    for (j=i+1,q=p->pNext;j<len;++j,q=q->pNext){
      if (p->data > q->data){
        t = p->data;
        p->data = q->data;
        q->data = t;
      }
    }
  }
}

2.快速排序

①算法的执行过程:

  • 快速排序是一种基于分治思想的交换排序
  • 在待排表L[1...n]中任取一个元素pivot作为枢轴(或基准)
    通过一趟排序将待排序表划分为两个部分L[1...k-1]L[k+1...n],使得L[1...k-1]中所有元素小于pivot,L[k+1...n]中所有元素大于pivot。
  • 此时的枢轴元素privot已经放到了最终的位置上。
  • 递归的对两个子表重复以上步骤,直到每个子表只有一个元素或为空。

②算法执行过程演示:


③算法代码:

//划分操作 
int Partition(ElemType A[], int low, int high){
  ElemType pivot = A[low];  //定义第一个元素为枢轴元素 
  while(low < high){
    while(low < high && A[high] > pivot) high--;
    A[low] = A[high];   //从后往前找到第一个小于枢轴的元素放到左边 
    while(low < high && a[low] < pivot) low++;
    A[high] = A[low];   //从前往后找到第一个大于枢轴的元素放到右边 
  }
  A[low] = pivot;       //将枢轴元素放到最终的位置 
  return low;         //返回枢轴元素的位置 
}
//递归的进行快排
void QuickSort(ElemType A[], int low, int high){
  if(low < high){             //如果两个指针的位置相反或者相等,表明递归结束 
    int pos = Partition(A, low, high);  //找到枢轴元素的位置 
    QuickSort(A, low, pos-1);     //递归排序左子表 
    QuickSort(A, pos+1, high);      //递归排序右子表 
  }
} 

④性能分析:

1.空间效率:由于快排是递归的,所以空间复杂度与递归深度有关。
二分递归空间复杂度最好情况下为O(log2n),最坏情况下进行n-1次调用,深度为O(n)
2.时间效率:与划分的好坏有关

最坏情况下每层递归都是最大限度的不对称(两个区域分别包含n-1和0个元素),复杂度为O(n2)也就是对应初始排序表基本有序或基本逆序

最理想情况每层递归能能完美的划分,复杂度为O(nlog2n)。得到的两个子问题规模都小于n/2

3.稳定性:不稳定

4.适用性:适用于线性表为顺序存储的情况。

相关文章
|
算法 搜索推荐
快速排序-数据结构与算法
快速排序(Quick Sort)是一种基于分治法的高效排序算法。其核心思想是通过选择基准(pivot),将数组划分为左右两部分,使得左侧元素均小于基准,右侧元素均大于基准,然后递归地对左右两部分进行排序。时间复杂度平均为 O(n log n),最坏情况下为 O(n²)(如数组已有序)。空间复杂度为 O(1),属于原地排序,但稳定性不佳。 实现步骤包括编写 `partition` 核心逻辑、递归调用的 `quickSort` 和辅助函数 `swap`。优化方法有随机化基准和三数取中法,以减少最坏情况的发生。
743 13
|
搜索推荐 C语言
数据结构(C语言)之对归并排序的介绍与理解
归并排序是一种基于分治策略的排序算法,通过递归将数组不断分割为子数组,直到每个子数组仅剩一个元素,再逐步合并这些有序的子数组以得到最终的有序数组。递归版本中,每次分割区间为[left, mid]和[mid+1, right],确保每两个区间内数据有序后进行合并。非递归版本则通过逐步增加gap值(初始为1),先对单个元素排序,再逐步扩大到更大的区间进行合并,直至整个数组有序。归并排序的时间复杂度为O(n*logn),空间复杂度为O(n),且具有稳定性,适用于普通排序及大文件排序场景。
|
存储 人工智能 算法
【C++数据结构——内排序】二路归并排序(头歌实践教学平台习题)【合集】
本关任务是实现二路归并算法,即将两个有序数组合并为一个有序数组。主要内容包括: - **任务描述**:实现二路归并算法。 - **相关知识**: - 二路归并算法的基本概念。 - 算法步骤:通过比较两个有序数组的元素,依次将较小的元素放入新数组中。 - 代码示例(以 C++ 为例)。 - 时间复杂度为 O(m+n),空间复杂度为 O(m+n)。 - **测试说明**:平台会对你编写的代码进行测试,提供输入和输出示例。 - **通关代码**:提供了完整的 C++ 实现代码。 - **测试结果**:展示代码运行后的排序结果。 开始你的任务吧,祝你成功!
455 10
|
搜索推荐 C++
【C++数据结构——内排序】快速排序(头歌实践教学平台习题)【合集】
快速排序是一种高效的排序算法,基于分治策略。它的主要思想是通过选择一个基准元素(pivot),将数组划分成两部分。一部分的元素都小于等于基准元素,另一部分的元素都大于等于基准元素。然后对这两部分分别进行排序,最终使整个数组有序。(第一行是元素个数,第二行是待排序的原始关键字数据。本关任务:实现快速排序算法。开始你的任务吧,祝你成功!
375 7
|
算法 搜索推荐 Shell
数据结构与算法学习十二:希尔排序、快速排序(递归、好理解)、归并排序(递归、难理解)
这篇文章介绍了希尔排序、快速排序和归并排序三种排序算法的基本概念、实现思路、代码实现及其测试结果。
595 1
|
算法 搜索推荐 Java
数据结构与算法学习十三:基数排序,以空间换时间的稳定式排序,速度很快。
基数排序是一种稳定的排序算法,通过将数字按位数切割并分配到不同的桶中,以空间换时间的方式实现快速排序,但占用内存较大,不适合含有负数的数组。
373 0
数据结构与算法学习十三:基数排序,以空间换时间的稳定式排序,速度很快。
|
算法 搜索推荐
数据结构与算法学习十一:冒泡排序、选择排序、插入排序
本文介绍了冒泡排序、选择排序和插入排序三种基础排序算法的原理、实现代码和测试结果。
583 0
数据结构与算法学习十一:冒泡排序、选择排序、插入排序
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
396 59
|
11月前
|
编译器 C语言 C++
栈区的非法访问导致的死循环(x64)
这段内容主要分析了一段C语言代码在VS2022中形成死循环的原因,涉及栈区内存布局和数组越界问题。代码中`arr[15]`越界访问,修改了变量`i`的值,导致`for`循环条件始终为真,形成死循环。原因是VS2022栈区从低地址到高地址分配内存,`arr`数组与`i`相邻,`arr[15]`恰好覆盖`i`的地址。而在VS2019中,栈区先分配高地址再分配低地址,因此相同代码表现不同。这说明编译器对栈区内存分配顺序的实现差异会导致程序行为不一致,需避免数组越界以确保代码健壮性。
228 0
栈区的非法访问导致的死循环(x64)
|
存储 C语言 C++
【C++数据结构——栈与队列】顺序栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现顺序栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 1.初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储
924 77

热门文章

最新文章