【基础算法】(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

相关文章

关于Java内存访问重排序的研究

《关于Java内存访问重排序的研究》文章主要介绍了重排序现象及其在多线程编程中的影响,包括内存可见性问题和Java内存模型中对重排序的规则... 目录什么是重排序重排序图解重排序实验as-if-serial语义内存访问重排序与内存可见性内存访问重排序与Java内存模型重排序示意表内存屏障内存屏障示意表Int

不懂推荐算法也能设计推荐系统

本文以商业化应用推荐为例,告诉我们不懂推荐算法的产品,也能从产品侧出发, 设计出一款不错的推荐系统。 相信很多新手产品,看到算法二字,多是懵圈的。 什么排序算法、最短路径等都是相对传统的算法(注:传统是指科班出身的产品都会接触过)。但对于推荐算法,多数产品对着网上搜到的资源,都会无从下手。特别当某些推荐算法 和 “AI”扯上关系后,更是加大了理解的难度。 但,不了解推荐算法,就无法做推荐系

康拓展开(hash算法中会用到)

康拓展开是一个全排列到一个自然数的双射(也就是某个全排列与某个自然数一一对应) 公式: X=a[n]*(n-1)!+a[n-1]*(n-2)!+...+a[i]*(i-1)!+...+a[1]*0! 其中,a[i]为整数,并且0<=a[i]<i,1<=i<=n。(a[i]在不同应用中的含义不同); 典型应用: 计算当前排列在所有由小到大全排列中的顺序,也就是说求当前排列是第

csu 1446 Problem J Modified LCS (扩展欧几里得算法的简单应用)

这是一道扩展欧几里得算法的简单应用题,这题是在湖南多校训练赛中队友ac的一道题,在比赛之后请教了队友,然后自己把它a掉 这也是自己独自做扩展欧几里得算法的题目 题意:把题意转变下就变成了:求d1*x - d2*y = f2 - f1的解,很明显用exgcd来解 下面介绍一下exgcd的一些知识点:求ax + by = c的解 一、首先求ax + by = gcd(a,b)的解 这个

综合安防管理平台LntonAIServer视频监控汇聚抖动检测算法优势

LntonAIServer视频质量诊断功能中的抖动检测是一个专门针对视频稳定性进行分析的功能。抖动通常是指视频帧之间的不必要运动,这种运动可能是由于摄像机的移动、传输中的错误或编解码问题导致的。抖动检测对于确保视频内容的平滑性和观看体验至关重要。 优势 1. 提高图像质量 - 清晰度提升:减少抖动,提高图像的清晰度和细节表现力,使得监控画面更加真实可信。 - 细节增强:在低光条件下,抖

【数据结构】——原来排序算法搞懂这些就行,轻松拿捏

前言:快速排序的实现最重要的是找基准值,下面让我们来了解如何实现找基准值 基准值的注释:在快排的过程中,每一次我们要取一个元素作为枢纽值,以这个数字来将序列划分为两部分。 在此我们采用三数取中法,也就是取左端、中间、右端三个数,然后进行排序,将中间数作为枢纽值。 快速排序实现主框架: //快速排序 void QuickSort(int* arr, int left, int rig

usaco 1.3 Mixing Milk (结构体排序 qsort) and hdu 2020(sort)

到了这题学会了结构体排序 于是回去修改了 1.2 milking cows 的算法~ 结构体排序核心: 1.结构体定义 struct Milk{int price;int milks;}milk[5000]; 2.自定义的比较函数,若返回值为正,qsort 函数判定a>b ;为负,a<b;为0,a==b; int milkcmp(const void *va,c

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]

poj 3974 and hdu 3068 最长回文串的O(n)解法(Manacher算法)

求一段字符串中的最长回文串。 因为数据量比较大,用原来的O(n^2)会爆。 小白上的O(n^2)解法代码:TLE啦~ #include<stdio.h>#include<string.h>const int Maxn = 1000000;char s[Maxn];int main(){char e[] = {"END"};while(scanf("%s", s) != EO

hdu 2602 and poj 3624(01背包)

01背包的模板题。 hdu2602代码: #include<stdio.h>#include<string.h>const int MaxN = 1001;int max(int a, int b){return a > b ? a : b;}int w[MaxN];int v[MaxN];int dp[MaxN];int main(){int T;int N, V;s