【数据结构】常见排序算法详细分析(内含java与c++代码)

【数据结构】常见排序算法详细分析(内含java与c++代码),第1张

【数据结构】常见排序算法详细分析(内含java与c++代码)

目录
  • 前言
  • 1. 定义
  • 2. 插入排序
    • 2.1 直接插入排序
    • 2.2 折半插入排序
    • 2.3 希尔排序
  • 3. 交换排序
    • 3.1 冒泡排序
    • 3.2 快速排序
  • 4. 选择排序
    • 4.1 简单选择排序
    • 4.2 堆排序
  • 5. 归并排序
  • 6. 总结

前言

排序是计算机程序设计中的一种重要 *** 作, 在很多领域中都有广泛的应用
在考研复试和企业面试都会有很强的考察需求

1. 定义

排序(Sorting) :是按关键字的非递减或非递增顺序对一组记录重新进行排列的 *** 作

关于排序的稳定性标准
定义如下:
假设 Ki=kj (i与j 都是从1到n,但两者不能同时相等),且在排序前的序列中 Ri领先于 Rj (即
i 后的序列中Rj领先于Rj;, 则称所用的排序方法是不稳定的。注意,排序算法的稳定性是针对所有
记录而言的

关于排序的分类:
关于排序的分类有很多种用法
这里从宏观上讲
主要分为内部排序与外部排序

  • 内部排序:待排序记录全部存放在计算机内存中进行排序的过程
  • 外部排序:待排序记录的数量很大,以致内存一次不能容纳全部记录, 在排序过程中尚需对外存进行访问的排序过程

内部排序可以分为:

  1. 插入类:将无序子序列中的一个或几个记录 "插入” 到有序序列中, 从而增加记录的有
    序子序列的长度。 主要包括直接插入排序、折半插入排序和希尔排序。
  2. 交换类:通过 “交换“ 无序序列中的记录从而得到其中关键字最小或最大的记录,并将
    它加入到有序子序列中,以此方法增加记录的有序子序列的长度。主要包括冒泡排序和快速排序。
  3. 选择类:从记录的无序子序列中 ”选择“ 关键字最小或最大的记录,并将它加入到有序子序
    列中, 以此方法增加记录的有序子序列的长度。 主要包括简单选择排序、树形选择排序和堆排序。
  4. 归并类:通过 “归并“ 两个或两个以上的记录有序子序列, 逐步增加记录有序序列的长
    度。2-路归并排序是最为常见的归并排序方法。
  5. 分配类:是唯一一类不需要进行关键字之间比较的排序方法, 排序时主要利用分配和收
    集两种基本 *** 作来完成。基数排序是主要的分配类排序方法

以下讲解的算法都是基于顺序表,且都是整数格式
具体定义如下:

可看一下c++的定义结构,其他语言也类似

#define MAXSIZE 20  //顺序表的最大长度
typedef int KeyType;  //定义关键字类型为整型
typedef struct{  //关键字项
	KeyType key; //其他数据项
	InfoType otherinfo; //记录类型
) RedType; 
typedef struct{ 
	RedType r[MAXSIZE+1]; //闲置或用做哨兵单元
	int length; //顺序表长度
) SqList; 

关于排序的评价指标好坏:
分别为时间复杂度和空间复杂度

2. 插入排序

插入排序的基本思想是:每一趟将一个待排序的记录,按其关键字的大小插入到已经排好序的一组记录的适当位置上,直到所有待排序记录全部插入为止

分别为:直接插入排序、折半插入排序和希尔排序

2.1 直接插入排序

将一条记录插入到已排好序的有序表中,从而得到一个新的、 记录数量增1的有序表

非代码块的表示如下:

算法逻辑思路:

最终的代码为:

i为1到n(不包括n)的范围,则j刚开始的初始为i-1
遍历轮换位置的时候为arr[j + 1] = arr[j];
x为哨兵,存放要插入的值
java版本

public class InsertSort {

    public static void sort(int[] arr) {
        if (arr.length >= 2) {
            for (int i = 1; i < arr.length; i++) {
                //挖出一个要用来插入的值,同时位置上留下一个可以存新的值的坑
                int x = arr[i];
                int j = i - 1;
                //在前面有一个或连续多个值比x大的时候,一直循环往前面找,将x插入到这串值前面
                while (j >= 0 && arr[j] > x) {
                    //当arr[j]比x大的时候,将j向后移一位,正好填到坑中
                    arr[j + 1] = arr[j];
                    j--;
                }
                //将x插入到最前面
                arr[j + 1] = x;
            }
        }
    }
}

c++版本

void InsertSort(SqList &L) 
{//对顺序表L做直接插入排序
	for(i=2;i<=L.length;++i){	
	if(L.r[i].key 
2.2 折半插入排序 

折半查找其实就是在查找插入位置的时候采用二分查找算法
折半插入算法是(直接插入排序+二分查找)

算法思想:
将数据分为有序数据和无序数据
第一次排序时默认Array[0]为有序数据,Array[1]~Array[n-1]为无序数据,有序数据分区的第一个元素位置为low,最后一个元素的位置为high

其逻辑思路写在纸上就是

java版本

import java.util.Arrays;
 
public class BinaryInsertSort {
    //这般插入排序
    public static void main(String[] args) {
        int[] arr = new int[]{5,1,0,3,4,2,7,9,8,6};
        System.out.println("排序之前:"+ Arrays.toString(arr));
        binaryInsertSort(arr);
        System.out.println("排序之后:"+ Arrays.toString(arr));
    }
 
    public static void binaryInsertSort(int[] arr){
        int temp;
        int low, high, mid;
        for (int i = 1; i < arr.length; i++){
            temp = arr[i];
            low = 0; high = i - 1;
            while (low <= high){
                mid = (low + high)/2;
                if (temp < arr[mid])
                    high = mid - 1;
                else
                    low = mid + 1;
            }
            for (int j = i - 1; j >= high + 1; j-- ){
                arr[j + 1] =arr[j];
            }
            arr[high + 1] = temp;
        }
    }
}

c++版本

void Binsert Sort(SqList &L) 
{//对顺序表L做折半插入排序
	for (i=2; i < =L. length; ++i) 
	{
		L.r[O]=L.r[i];//将待插人的记录暂存到监视哨中
		low=1;high=i-1; //置查找区间初值
		while(low<=high)//在r[low .. high]中折半查找插入的位置
		{m=(low+high)/2;  	//折半
		if(L.r[O].key=high+1; --j) L.r[j+1]=L.r(j]; //记录后移

		L.r[high+1]=L.r[O]; //将r[O]即原r[i], 插入到正确位置
	}
}
2.3 希尔排序

希尔排序(Shell’sSort)又称 ”缩小增量排序" (Diminishing Increment Sort), 是插入排序的一种

算法思想:
希尔排序实质上是采用分组插入的方法。先将整个待排序记录序列分割成几组,从而减少参与
直接插入排序的数据量,对每组分别进行直接插入排序,然后增加每组的数据量,重新分组。 这样
当经过几次分组排序后,整个序列中的记录 “基本有序” 时,再对全体记录进行一次直接插入排序

希尔对记录的分组,不是简单地 ”逐段分割", 而是将相隔某个 “增量” 的记录分成一组。

非代码演示其逻辑:

  1. 第一趟取增量 d1 =5 , 所有间隔为 5 的记录分在同一组,全部记录分成 5 组, 在各个组
    中分别进行直接插入排序
  2. 第二趟取增量 d2 = 3, 所有间隔为 3 的记录分在同一组, 全部记录分成 3 组,在各个组
    中分别进行直接插入排序
  3. 第三趟取增量 d3 = 1, 对整个序列进行一趟直接插入排序, 排序完成

使用三个for

  • 最外层for用来做步长的循环判断,并且一半半减半
  • 第二层for,如果确定好了步长,第二层for用来确定右区间,并且一步步增长
  • 第三层for,也就是内层for,用来确定左区间,并且交换其左右区间,也要一步步和右区间增长

java版本

public static void main(String[] args) {

        int arr[] = {7, 5, 3, 2, 4};

        //希尔排序(插入排序变种版),i层循环控制步长
        for (int i = arr.length / 2; i > 0; i /= 2) {
            //j控制无序端的起始位置,并且一步步往右扩充,但不能超过完整长度
            for (int j = i; j < arr.length; j++) {
                //有区间一步步增长:k=j,左区间一步步增长:k=k-i=j-i=i-i=0;
                for (int k = j; k > 0  && k - i >= 0; k -= i) {
                    if (arr[k] < arr[k - i]) {
                        int temp = arr[k - i];
                        arr[k - i] = arr[k];
                        arr[k] = temp;
                    } else {
                        break;
                    }
                }
            }
            //j,k为插入排序,不过步长为i
        }
    }

c++版本

void Shellinsert(SqList &L, int dk) 
{//对顺序表L做一趟增量是dk的希尔插入排序
for(i=dk+1;i<=L.length;++i) 
{
	if(L.r[i].keyO&& L.r[O].key 

js版本
此处来源于
舍友的js代码

A = [1, 5, 4, 9, 3, 6, 7, 50, 60, 5, 2];

function shell(A) {
  for (let dk = Math.floor(A.length / 2); dk >= 1; dk = Math.floor(dk / 2)) {
    for (let i = dk; i < A.length; i++) {
      let k = i;
      let temp = A[k];
      while (temp < A[k - dk] && k > 0) {
        A[k] = A[k - dk];
        k = k - dk;
      }
      A[k] = temp;
    }
  }
  return A;
}
console.log(shell(A));
3. 交换排序

交换排序的基本思想是:两两比较待排序记录的关键字,一旦发现两个记录不满足次序要求时则进行交换,直到整个序列全部满足要求为止

3.1 冒泡排序

冒泡排序(Bubble Sort)是一种最简单的交换排序方法,它通过两两比较相邻记录的关键字,如果发生逆序,则进行交换,从而使关键字小的记录如气泡一般逐渐往上 "漂浮"(左移),或者使关键字大的记录如石块一样逐渐向下 "坠落”(右移)

类似的算法逻辑如下

java版本

  • 右移的版本,也就是先放最大的数字

外层循环控制循环的次数,一次次缩小,没有自身
内层循环控制循环的个数,随着外层循环的增加,内层循环会越来越小
类似这种数字

public static void main(String[] args) {

        int arr[] = {4,5,6,3,2,1};

        //冒泡
        for (int i = 0; i < arr.length-1; i++) {
            //外层循环,遍历次数
            for (int j = 0; j < arr.length - i - 1; j++) {
                //内层循环,升序(如果前一个值比后一个值大,则交换)
                //内层循环一次,获取一个最大值
                if (arr[j] > arr[j + 1]) {
                    int temp = arr[j + 1];
                    arr[j + 1] = arr[j];
                    arr[j] = temp;
                }
            }
        }
    }
  • 左移版本,先弄最小数字

外层循环时0到n-1,因为时比较最小数字,所以要留一个数字和右边的比较,不能是n与自已比较,是n-1与n比较
内层循环是比较

int arr[] = {4,5,6,3,2,1};

int b =0;
for(int i=arr.length;i>1;i--) { //从大开始循环 从后面开始比较
	for(int j=0;j
  • 进化版
    加入一个布尔变量,如果内循环没有交换值,说明已经排序完成,提前终止
  • public static void sortPlus(int[] arr){
        if(arr != null && arr.length > 1){
            for(int i = 0; i < arr.length - 1; i++){
                // 初始化一个布尔值
                boolean flag = true;
                for(int j = 0; j < arr.length - i - 1 ; j++){
                    if(arr[j] > arr[j+1]){
                        // 调换
                        int temp;
                        temp = arr[j];
                        arr[j] = arr[j+1];
                        arr[j+1] = temp;
                        // 改变flag
                        flag = false;
                    }
                }
                if(flag){
                    break;
                }
            }
        }
    }
    

    c++版本

    void BubbleSort(SqList &L) 
    {//对顺序表L做冒泡排序
    	m=L.length-1;flag=1; //flag用来标记某一趟排序是否发生交换
    	while ((m>O) && (flag==1)) 
    	{ 
    		flag=O; //flag置为0, 如果本趟排序没有发生交换,则不会执行下一趟排序
    		for (j=1;j<=m;j++) 
    		{
    			if(L.r[j].key>L.r[j+1].key) 
    			{ 
    			flag=1; //flag置为1, 表示本趟排序发生了交换
    			t=L.r[j]; L.r[j]=L.r[j+1]; L.r[j+1]=t;//交换前后两个记录
    			} //if
    		m--;
    	}
    }
    
    3.2 快速排序

    快速排序 (Quick Sort) 是由冒泡排序改进而得的。 在 冒泡排序过程中, 只对相邻的两个记录进行比较, 因此每次交换两个相邻记录时只能消除一个逆序。 如果能通过两个(不相邻)记录的一次交换,消除多个逆序, 则会大大加快排序的速度。 快速排序方法中的一次交换可能消除多个逆序

    算法思想:
    在待排序的 n个记录中任取一个记录(通常取第一个记录)作为枢轴(或支点),设其关键字为
    pivotkey。经过一趟排序后,把所有关键字小千pivotkey 的记录交换到前面,把所有关键字大于pivotkey
    的记录交换到后面,结果将待排序记录分成两个子表,最后将枢轴放置在分界处的位置。然后,分别
    对左、右子表重复上述过程,直至每一子表只有一个记录时,排序完成

    具体步骤如下:

    1. 选择待排序表中的第一个记录作为枢轴,将枢轴记录暂存在 r[0]的位置上。附设两个指针 low
      和 high, 初始时分别指向表的下界和上界(第一趟时, low = 1; high= L.length;)。
    2. 从表的最右侧位置依次向左搜索 ,找到第一个关键字小于枢轴关键字 pivotkey 的记录,将其移到 low 处。具体 *** 作是:当 low
    3. 然后再从表的最左侧位置,依次向右搜索找到第一个关键字大于 pivotkey 的记录和枢轴记录交换。具体 *** 作是:当 low
    4. 重复步骤2和3, 直至 low 与 high 相等为止。此时 low 或 high 的位置即为枢轴在此趟排序中的最终位置, 原表被分成两个子表

    注意事项
    一定要移动j在移动i。
    不能先移动i指针,如果本身是已经排序好的序列,那么i就直接移动到最后一格停止,然后arr[low]和arr[i]再交换就错了。而先移右边,即使j直接移到了第一个low,那么arr[low]和arr[i]就是同一个元素,交换也没影响

    另一种算法,通俗易懂的算法步骤:

    1. 设置两个指针left和right分别指向数组的头部和尾部,并且以头部的元素为基准数

    2. right指针先往左移动,找到小于基准数的元素就停下,然后准备移动left指针

    3. left指针往右移动,找到大于基准数的元素就停下,然后交换right和left指针所值元素的值

    重复第二、三步,直到两个指针left和right重合

    1. 两个指针重合后将基准数与两个指针指向的元素值交换

    java版本

    public class QuickSort {
        public static void quickSort(int[] arr,int low,int high){
            int i,j,temp,t;
            if(low>high){
                return;
            }
            i=low;
            j=high;
            //temp就是基准位
            temp = arr[low];
     
            while (i=arr[i]&&i 
    

    c++版本

    int Partition(SqList &L, int low, int high) 
    {//对顺序表1中的子表r[low .. high)进行一趟排序,返回枢轴位置 
    	L.r[O]=L.r[low]; //用子表的第一个记录做枢轴记录
    	pivotkey=L.r[low].key; //枢轴记录关键字保存在pivotkey中
    	while(low=pivotkey) --high;
    		L.r[low]=L.r[high]; //将比枢轴记录小的记录移到低端
    		while(low 
    4. 选择排序 
    

    选择排序的基本思想是:每一趟从待排序的记录中选出关键字最小的记录,按顺序放在已排序的记录序列的最后,直到全部排完为止

    4.1 简单选择排序

    简单选择排序 (SimpleSelection Sort)也称作直接选择排序

    算法思想:

    1. 设待排序的记录存放在数组r[l… n]中。第一趟从r[I]开始,通过n-1次比较,从n个记录中选出关键字最小的记录,记为r[k], 交换r[l]和r[k]。
    2. 第二趟从r[2]开始,通过n-2次比较,从n-1个记录中选出关键字最小的记录,记为r[k],交换r[2]和r[k]
    3. 依次类推,第i趟从r[i]开始,通过n-i次比较,从n-i+l个记录中选出关键字最小的记录,记为r[k], 交换r[i]和r[k]。
    4. 经过n-1趟,排序完成。


    java版本

    int min;//将当前的i暂定为是最小的数的位置
    int tmp;//用于交换
    
    for (int i = 0; i < a.length; i++) {
        min= i;
        for (int j = i + 1; j < a.length; j++) {
            if (a[min] > a[j]) {
                min = j;//把较小的数放到前面,但是目前仍未进行交换,因为还没有找出最小的那个数
            }
        }
        tmp = a[min];
        a[min] = a[i];
        a[i] = tmp;
    
    }
    

    c++版本

    void SelectSort(SqList &L) 
    {//对顺序表L做简单选择排序
    	for(i=1;i 
    4.2 堆排序 
    

    堆排序 (Heap Sort) 是一种树形选择排序,在排序过程中,将待排序的记录 r[l…n]看成是一棵完全二叉树的顺序存储结构,利用完全二叉树中双亲结点和孩子结点之间的内在关系,在当前无序的序列中选择关键字最大(或最小)的记录

    顾名思义, 就是将数据以堆的结构, 或者说类似于二叉树的结构, 每次都整理二叉树将最大值或者最小值找到, 然后放到数组末尾

    显然,在这两种堆中 ,堆顶元素(或 完全二叉树的根)必为序列中 n个元素的最大值(或最小值),分别称之为大根堆和小根堆

    5. 归并排序

    归井排序(Merging Sort)就是将两个或两个以上的有序表合并成一个有序表的过程。将两个有序表合并成一个有序表的过程称为2-路归井,2-路归并 最为简单和常用

    基本思想其实就是分治算法,将待排序元素分成大小大致相同的2个子集合,分别对2个子集合进行排序,最终将排好序的子集合合并成为所要求的排好序的集合。

    查看其归并排序的书籍上
    伪代码是这样写的

    整体代码思路如下

    这是分治的”治“算法部分逻辑

    所谓的“治”算法 非代码演示如下

    java版本

    public class Main {
     
    	public static void main(String[] args) {
    		int[] arr = {11,44,23,67,88,65,34,48,9,12};
    		int[] tmp = new int[arr.length];    //新建一个临时数组存放
    		mergeSort(arr,0,arr.length-1,tmp);
    		for(int i=0;i 
    

    c++版本

    void Merge{RedType R[],RedType &T[],int low,int mid,int high) 
    {//将有序表 R[low.. mid]和R[mid+l. .high]归并为有序表 T[low.. high]
    	i=low;j=mid+l;k=low; 
    	while(i<=mid&&j<=high) //将R中记录由小到大地并入T中
    	{
    		if (R[i].key<=R[j].key) T[k] =R[i++]; 
    		else T[k)=R[j++]; 
    	}
    	while(i<=mid) T[k++]=R[i++]; //将剩余的 R[low.. mid]复制到 T中
    	while (j<=high) T [k++]=R[j++]; //将剩余的 R[j.high]复制到 T 中
    }
    
    6. 总结

    其复杂度在面试中也会经常问到

    欢迎分享,转载请注明来源:内存溢出

    原文地址: http://outofmemory.cn/zaji/5659628.html

    (0)
    打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
    上一篇 2022-12-16
    下一篇 2022-12-16

    发表评论

    登录后才能评论

    评论列表(0条)

    保存