【基础算法】(01) 十二种排序算法(第一篇)

2024-06-06 17:58

本文主要是介绍【基础算法】(01) 十二种排序算法(第一篇),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

【基础算法】(01) 十二种排序算法(第一篇)

Auther: Thomas Shen
E-mail: Thomas.shen3904@qq.com
Date: 2017/10/20
All Copyrights reserved !

      • 基础算法01 十二种排序算法第一篇
        • 总述
        • 直接插入排序 Straight Insertion Sort
        • 二分插入排序 Binary insert sort
        • 希尔Shell排序
        • 选择排序简单选择排序Simple Selection Sort
        • 选择排序堆排序Heap Sort
        • 交换排序冒泡排序Bubble Sort
        • 鸡尾酒排序双向冒泡排序
        • 交换排序快速排序Quick Sort
        • 归并排序Merge Sort
        • 桶排序 Bucket sort
        • 计数排序 Counting sort
        • 基数排序 Radix Sort
        • 总结
        • References


1. 总述:

本系列总结了常用的十二种排序算法,每个算法都包括算法原理, 代码实现, 面试例题 三部分。

其中本文是排序算法系列的第一篇,介绍了三种插入排序方法:

  • 2. 直接插入排序 (Straight Insertion Sort),
  • 3. 二分插入排序 (Binary Insert Sort),
  • 4. 希尔排序 (Shell’s Sort)

排序有内部排序和外部排序:

这里写图片描述

当n较大,则应采用时间复杂度为O(nlog2n)的排序方法:快速排序、堆排序或归并排序序。

快速排序:是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短;

排序算法大体可分为两种:

  • 一种是比较排序,时间复杂度O(nlogn) ~ O(n^2),主要有:
    • 直接插入排序,希尔排序,
    • 冒泡排序,快速排序,
    • 简单选择排序,堆排序,
    • 归并排序,等。
  • 另一种是非比较排序,时间复杂度可以达到O(n),主要有:
    • 计数排序,基数排序,桶排序等。

这里写图片描述


2. 直接插入排序 (Straight Insertion Sort):

2.1 原理简介:
插入排序(Insertion Sort)的算法描述是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。

算法描述和分析:
一般来说,插入排序都采用in-place在数组上实现。具体算法描述如下:

  1. 从第一个元素开始,该元素可以认为已经被排序;
  2. 取出下一个元素,在已经排序的元素序列中从后向前扫描;
  3. 如果该元素(已排序)大于新元素,将该元素移到下一位置;
  4. 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;
  5. 将新元素插入到该位置后;
  6. 重复步骤2~5。

这里写图片描述

如果碰见一个和插入元素相等的,那么插入元素把想插入的元素放在相等元素的后面。所以,相等元素的前后顺序没有改变,从原无序序列出去的顺序就是排好序后的顺序,所以插入排序是稳定的

如果目标是把n个元素的序列升序排列,那么采用插入排序存在最好情况和最坏情况。最好情况就是,序列已经是升序排列了,在这种情况下,需要进行的比较操作需(n-1)次即可。最坏情况就是,序列是降序排列,那么此时需要进行的比较共有n(n-1)/2次。插入排序的赋值操作是比较操作的次数减去(n-1)次。

平均来说插入排序算法复杂度为O(n^2)。因而,插入排序不适合对于数据量比较大的排序应用。但是,如果需要排序的数据量很小,例如,量级小于千,那么插入排序还是一个不错的选择。 插入排序在工业级库中也有着广泛的应用,在STL的sort算法和stdlib的qsort算法中,都将插入排序作为快速排序的补充,用于少量元素的排序(通常为8个或以下)。

时间复杂度:O(n^2);

2.2 代码实现:

void print(int a[], int n ,int i){  cout<<i <<":";  for(int j= 0; j<8; j++){  cout<<a[j] <<" ";  }  cout<<endl;  
}  void InsertSort(int a[], int n)  
{  for(int i= 1; i<n; i++){  if(a[i] < a[i-1]){ //若第i个元素大于i-1元素,直接插入。小于的话,移动有序表后插入  int j= i-1;   int x = a[i];  //复制为哨兵,即存储待排序元素  a[i] = a[i-1]; //先后移一个元素  while(x < a[j]){  //查找在有序表的插入位置  a[j+1] = a[j];  j--;         //元素后移  }  a[j+1] = x;      //插入到正确位置  }  print(a,n,i);        //打印每趟排序的结果  }  
}  int main(){  int a[8] = {3,1,5,7,2,4,9,6};  InsertSort(a,8);  print(a,8,8);  
}  

2.3 面试例题:

2.3.1 链表的插入排序程序:

template<typename T>  
struct list_node  
{  struct list_node<T> *next;  T value;  
};  
template<typename T>  
struct _list  
{  struct list_node<T> *head;  int size;  
};  
template<typename T>  
void SortLink(struct _list<T> * link) {  struct list_node<T> *pHead,*pRear,*p,*tp;  if (!link) return;  for (pHead=link->head,pRear=0;pHead;pHead=pHead->next) {  for (tp=pHead,p=pHead->next;p;tp=p,p=p->next)  if (pHead->value>=p->value)  tp->next=p->next,p->next=pHead,pHead=p,p=tp;  if (!pRear) link->head=pHead;  else pRear->next=pHead;  pRear=pHead;  }  
}  

3. 二分插入排序 (Binary insert sort):

二分(折半)插入(Binary insert sort)排序是一种在直接插入排序算法上进行小改动的排序算法。其与直接排序算法最大的区别在于查找插入位置时使用的是二分查找的方式,在速度上有一定提升。

3.1 原理简介:

一般来说,插入排序都采用in-place在数组上实现。具体算法描述如下:

  1. 从第一个元素开始,该元素可以认为已经被排序;
  2. 取出下一个元素,在已经排序的元素序列中二分查找到第一个比它大的数的位置;
  3. 将新元素插入到该位置后;
  4. 重复上述两步。
  • 稳定
  • 空间代价:O(1)
  • 时间代价:插入每个记录需要O(log i)比较,最多移动i+1次,最少2次。最佳情况O(n log n),最差和平均情况O(n^2)。

二分插入排序是一种稳定的排序。当n较大时,总排序码比较次数比直接插入排序的最差情况好得多,但比最好情况要差,所元素初始序列已经按排序码接近有序时,直接插入排序比二分插入排序比较次数少。二分插入排序元素移动次数与直接插入排序相同,依赖于元素初始序列。

3.2 代码实现:

void BinInsertSort(int a[], int n)   
{   int key, left, right, middle;   for (int i=1; i<n; i++)   {   key = a[i];left = 0;   right = i-1;   while (left<=right){   middle = (left+right)/2;   if (a[middle]>key)   right = middle-1;   elseleft = middle+1;}for(int j=i-1; j>=left; j--)   {   a[j+1] = a[j];}              a[left] = key;          }   
}  

4. 希尔(Shell)排序:

希尔排序(Shell`s Sort)相对直接排序有较大的改进。希尔排序又叫缩小增量排序。

4.1 原理简介:

  1. 选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;
  2. 按增量序列个数k,对序列进行k 趟排序;
  3. 每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。

希尔排序

4.2 代码实现:

我们简单处理增量序列:增量序列d = {n/2 ,n/4, n/8 …..1} n为要排序数的个数

即:先将要排序的一组记录按某个增量d(n/2,n为要排序数的个数)分成若干组子序列,每组中记录的下标相差d.对每组中全部元素进行直接插入排序,然后再用一个较小的增量(d/2)对它进行分组,在每组中再进行直接插入排序。继续不断缩小增量直至为1,最后使用直接插入排序完成排序。
void print(int a[], int n ,int i){  cout<<i <<":";  for(int j= 0; j<8; j++){  cout<<a[j] <<" ";  }  cout<<endl;  
}  
/** * 直接插入排序的一般形式 * @param int dk 缩小增量,如果是直接插入排序,dk=1 */  void ShellInsertSort(int a[], int n, int dk)  
{  for(int i= dk; i<n; ++i){  if(a[i] < a[i-dk]){          //若第i个元素大于i-1元素,直接插入。小于的话,移动有序表后插入  int j = i-dk;     int x = a[i];           //复制为哨兵,即存储待排序元素  a[i] = a[i-dk];         //首先后移一个元素  while(x < a[j]){     //查找在有序表的插入位置  a[j+dk] = a[j];  j -= dk;             //元素后移  }  a[j+dk] = x;            //插入到正确位置  }  print(a, n,i );  }  }  /** * 先按增量d(n/2,n为要排序数的个数进行希尔排序 */  
void shellSort(int a[], int n){  int dk = n/2;  while( dk >= 1  ){  ShellInsertSort(a, n, dk);  dk = dk/2;  }  
}  int main(){  int a[8] = {3,1,5,7,2,4,9,6};  //ShellInsertSort(a,8,1); //直接插入排序  shellSort(a,8);           //希尔插入排序  print(a,8,8);  
}  

希尔排序时效分析很难,关键码的比较次数与记录移动次数依赖于增量因子序列d的选取,特定情况下可以准确估算出关键码的比较次数和记录的移动次数。目前还没有人给出选取最好的增量因子序列的方法。增量因子序列可以有各种取法,有取奇数的,也有取质数的。

但需要注意:增量因子中除1 外没有公因子,且最后一个增量因子必须为1。

希尔排序方法是一个不稳定的排序方法。


5. 选择排序—简单选择排序(Simple Selection Sort):
6. 选择排序—堆排序(Heap Sort):
7. 交换排序—冒泡排序(Bubble Sort):
8. 鸡尾酒排序/双向冒泡排序:
9. 交换排序—快速排序(Quick Sort):
10. 归并排序(Merge Sort):
11. 桶排序 (Bucket sort):
12. 计数排序 (Counting sort):
13. 基数排序 (Radix Sort):

参见第二、三、四篇;


14. 总结:

这里写图片描述


References. :
  • [ 1 ]. Coursera | Java程序设计 | PKU
  • [ 2 ]. 转载自:八大排序算法
  • [ 3 ]. 转载自:12种排序算法详解

这篇关于【基础算法】(01) 十二种排序算法(第一篇)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1036820

相关文章

从基础到高级详解Python数值格式化输出的完全指南

《从基础到高级详解Python数值格式化输出的完全指南》在数据分析、金融计算和科学报告领域,数值格式化是提升可读性和专业性的关键技术,本文将深入解析Python中数值格式化输出的相关方法,感兴趣的小伙... 目录引言:数值格式化的核心价值一、基础格式化方法1.1 三种核心格式化方式对比1.2 基础格式化示例

redis-sentinel基础概念及部署流程

《redis-sentinel基础概念及部署流程》RedisSentinel是Redis的高可用解决方案,通过监控主从节点、自动故障转移、通知机制及配置提供,实现集群故障恢复与服务持续可用,核心组件包... 目录一. 引言二. 核心功能三. 核心组件四. 故障转移流程五. 服务部署六. sentinel部署

从基础到进阶详解Python条件判断的实用指南

《从基础到进阶详解Python条件判断的实用指南》本文将通过15个实战案例,带你大家掌握条件判断的核心技巧,并从基础语法到高级应用一网打尽,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一... 目录​引言:条件判断为何如此重要一、基础语法:三行代码构建决策系统二、多条件分支:elif的魔法三、

Python WebSockets 库从基础到实战使用举例

《PythonWebSockets库从基础到实战使用举例》WebSocket是一种全双工、持久化的网络通信协议,适用于需要低延迟的应用,如实时聊天、股票行情推送、在线协作、多人游戏等,本文给大家介... 目录1. 引言2. 为什么使用 WebSocket?3. 安装 WebSockets 库4. 使用 We

从基础到高阶详解Python多态实战应用指南

《从基础到高阶详解Python多态实战应用指南》这篇文章主要从基础到高阶为大家详细介绍Python中多态的相关应用与技巧,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录一、多态的本质:python的“鸭子类型”哲学二、多态的三大实战场景场景1:数据处理管道——统一处理不同数据格式

C++归并排序代码实现示例代码

《C++归并排序代码实现示例代码》归并排序将待排序数组分成两个子数组,分别对这两个子数组进行排序,然后将排序好的子数组合并,得到排序后的数组,:本文主要介绍C++归并排序代码实现的相关资料,需要的... 目录1 算法核心思想2 代码实现3 算法时间复杂度1 算法核心思想归并排序是一种高效的排序方式,需要用

MySQL数据类型与表操作全指南( 从基础到高级实践)

《MySQL数据类型与表操作全指南(从基础到高级实践)》本文详解MySQL数据类型分类(数值、日期/时间、字符串)及表操作(创建、修改、维护),涵盖优化技巧如数据类型选择、备份、分区,强调规范设计与... 目录mysql数据类型详解数值类型日期时间类型字符串类型表操作全解析创建表修改表结构添加列修改列删除列

Python 函数详解:从基础语法到高级使用技巧

《Python函数详解:从基础语法到高级使用技巧》本文基于实例代码,全面讲解Python函数的定义、参数传递、变量作用域及类型标注等知识点,帮助初学者快速掌握函数的使用技巧,感兴趣的朋友跟随小编一起... 目录一、函数的基本概念与作用二、函数的定义与调用1. 无参函数2. 带参函数3. 带返回值的函数4.

python panda库从基础到高级操作分析

《pythonpanda库从基础到高级操作分析》本文介绍了Pandas库的核心功能,包括处理结构化数据的Series和DataFrame数据结构,数据读取、清洗、分组聚合、合并、时间序列分析及大数据... 目录1. Pandas 概述2. 基本操作:数据读取与查看3. 索引操作:精准定位数据4. Group

Java中的雪花算法Snowflake解析与实践技巧

《Java中的雪花算法Snowflake解析与实践技巧》本文解析了雪花算法的原理、Java实现及生产实践,涵盖ID结构、位运算技巧、时钟回拨处理、WorkerId分配等关键点,并探讨了百度UidGen... 目录一、雪花算法核心原理1.1 算法起源1.2 ID结构详解1.3 核心特性二、Java实现解析2.