数据结构中的几种排序算法与一些小知识

标题一、int和Integer的区别

1、Integer是int的包装类,int则是java的一种基本数据类型

2、Integer变量必须实例化后才能使用,而int变量不需要

3、Integer实际是对象的引用,当new一个Integer时,实际上是生成一个指针指向此对象;而int则是直接存储数据值

4、Integer的默认值是null,int的默认值是0

标题二、排序的概念与排序的方法

(一)排序的基本概念

1.排序就是重新排列表中的元素,使表中的元素满足按关键字有序的过程

2.排序算法的稳定性是指经过排序后,能使关键字相同的元素保持原顺序中的相对位置不变。若待排序表中有两个元素Ri和Rj,其对应关键字keyi = keyj,且在排序前Ri在Rj的前面,若使用某一排序算法排序后,Ri仍然在Rj的前面,则称这个排序算法是稳定的,否则称这个排序算法是不稳定的。

3.内部排序。是指在排序期间元素全部存放在内存中的排序。

4.外部排序。是指在排序期间元素无法全部同时存放在内存中,必须在排序的过程中根据要求不断地在内、外存之间移动的排序。

【注意】

1.对同一线性表使用不同的排序方法进行排序,得到的排序结果可能不同。事实上,当对线性表中的元素按关键字进行排序,不稳定的排序算法可能得到的排序结果不同。
2.二路归并排序中比较操作的执行次数为⌈ log ⁡ 2 ( n ! ) ⌉

注:对任意n个关键字排序的比较次数至少为⌈ log ⁡ 2 ( n ! ) ⌉

题:对任意7个关键字进行基于比较的排序,至少要进行13次(log2(7!))关键字之间的两两比较。

公式证明:假设整个排序过程需要做t次比较,则会产生2t种情况。由于n个元素则有n!种排列情况。因此2>=n!,即t>=log2(n!)。因为t为整数,故为⌈ log ⁡ 2 ( n ! ) ⌉

(二)插入排序

插入排序是一种简单直观的排序方法,其基本思想是每次将一个待排序的记录按其关键字大小插入到前面已经排序好的子序列中,直到全部记录插入完成。
|由插入思想可以引申出三个重要的排序方法:直接插入排序;折半插入排序;希尔排序(shell sort)| |
|--------------------------------------------------|–|
| | |

(1)直接插入排序

步骤:

为了实现L[1……n]的排序,可以将L(2)~L(N)以此插入到前面已经排好的子序列中
1.从第一个元素开始,将该元素认为已经被排序好的子序列
2.取下一个元素num,从已排序的元素序列从后往前扫描
3.如果该元素大于num,则将该元素移到下一位
4.重复步骤3,直到找到已排序元素中小于等于num的元素A[i]
5.num插入到该元素的后面,如果已排序所有元素都大于num,则将num插入到下标为0的位置
6.重复步骤2~5

动图演示如下:

在这里插入图片描述

思路:

在待排序的元素中,假设前n-1个元素已有序,现将第n个元素插入到前面已经排好的序列中,使得前n个元素有序。按照此法对所有元素进行插入,直到整个序列有序。
  但我们并不能确定待排元素中究竟哪一部分是有序的,所以我们一开始只能认为第一个元素是有序的,依次将其后面的元素插入到这个有序序列中来,直到整个序列有序为止。
在这里插入图片描述

直接插入算法的性能分析
空间效率

仅使用了常数个辅助单元,因而空间的复杂度为O(1)

时间效率

在排序过程中,插入进行了n-1趟(2—n个元素的插入);每趟都要插入和比较。
最好的情况下:表是有序的。每插入一个元素比较一次,不需要移动
最坏的情况下:逆序排列。所以时间复杂度为:O(n^2)

稳定性

是一种稳定的排序算法

适用性

适用于顺序存储与链式存储(链式存储时可以从前往后查找元素的指定位置)

代码如下:
public class Main {
    public static void main(String[] args) {
        int[] arr={5,5,4,3,2,1};
        InsertSort(arr,5);
        System.out.println();
    }
    private static void InsertSort(int A[], int n) {
        int i, j;
        for (i = 2; i <= n; i++) {                //依次插入2-n
            if (A[i - 1] > A[i]) {                //若A[i]关键码小于其前驱元素,将A[i]插入到有序表
                A[0] = A[i];                       //A[0]复制为哨兵,并不存放元素
                for (j = i - 1; A[0] < A[j]; j--)    //A[0]最小为A[0] 所以此时A[0]不会往后移动
                    A[j + 1] = A[j];                   //向后挪动
                A[j + 1] = A[0];                      //复制到插入位置
            }

            for (int m = 0; m <= n; m++)
                System.out.print(A[m] + " ");
            System.out.println();
        }
    }
}
结果

第一列为哨兵 后面的为每次的排序结果
在这里插入图片描述

(2)折半插入排序

思路

插入的基本步骤为:1.从前面的子序列中找到待插入位置 2.给插入位置腾出位置

直接插入:总是边比较边移动元素

折半插入:先找到待插入位置,然后执行插入操作

折半插入主要是减少了比较的次数,所以时间复杂度仍为O(n^2)

稳定性:是一种稳定的算法

适用性:适用于顺序存储的线性表

代码

public class Main1 {
    public static void main(String[] args) {
        int[] arr={5,5,4,3,2,1};
        InsertSort(arr,5);
        System.out.println();
    }
    private static void InsertSort(int A[], int n) {
        int i, j,low, high, mid;
        for (i = 2; i <= n; i++) {              //依次将2~n插入到子序列
                A[0] = A[i];                    //将A[i]暂存到A[0]
                low=1;high=i-1;                 //设置查找范围
                while (low<=high ) {
                    mid=(low+high)/2;
                    if (A[mid] >A[0])
                        high=mid-1;             //查找左半子表
                    else
                        low = mid+1;            //查找右半子表
                }
                
                for (j = i - 1; j>=high+1; j--)
                    A[j + 1] = A[j];                             //统一后移元素,空出插入位置
                A[high + 1] = A[0];                             //插入操作
                
                //打印整个排序过程
            for (int m = 0; m <= n; m++)
                System.out.print(A[m] + " ");
            System.out.println();
            }
        }
}

(3)希尔排序

希尔排序的改进

直接插入排序为正序时,复杂度最高;由此可见它更适用于基本有序的排序表和数据量不大的排序表。希尔排序正是基于这两点分析对直接插入排序进行改进,又称为缩小增量排序

基本思想为

先将待排序列分割成L[i,i+d,i+2d,…,i+kd]的“特殊”子表,即把相隔某个“增量的”记录组成一个子表,对各个子表分别进行直接插入排序,当整个表中的元素已呈现基本有序时,再对全体进行一次直接插入排序。

步骤

1.先选定一个小于N的整数gap作为第一增量,然后将所有距离为gap的元素分在同一组,并对每一组的元素进行直接插入排序。然后再取一个比第一增量小的整数作为第二增量,重复上述操作…
2.当增量的大小减到1时,就相当于整个序列被分到一组,进行一次直接插入排序,排序完成。

动图如下

在这里插入图片描述

希尔排序算法的性能分析
空间效率

仅使用常数个辅助单元,因而空间复杂度为O(1)

时间效率

当n在某个特定的范围时,希尔排序的时间复杂度约为O(n^1.3)
最坏的情况下希尔排序的时间复杂度为O(n^2)

稳定性

不稳定

适用性

希尔排序算法只适用于线性表为顺序存储的情况

代码
public class Main2 {
    public static void main(String[] args) {
        int[] arr={5,5,4,3,2,1};
        ShellSort(arr,5);
        System.out.println();
    }
    private static void ShellSort(int A[], int n) {
        int j;
        for (int dk=n/2;dk>=1;dk=dk/2){             //步长变化
            for (int i=dk+1; i <= n; i++){
                if (A[i]<A[i-dk]) {                     //需将A【i】插入有序增量子表
                    A[0]=A[i];                              //暂存在A【0】
                    for (j=i-dk; j>0 && A[0]<A[j]; j-=dk)   //记录后移元素。找到插入位置
                        A[j+dk] = A[j];
                    A[j+dk]=A[0];                            //插入
                }
            }
            //输出排序过程
            for (int m = 1; m <= n; m++)
                System.out.print(A[m] + " ");
            System.out.println();
        }
    }
}

(三)交换排序

(1)冒泡排序(bubble sort)

思路

左边大于右边交换一趟排下来最大的在右边

动图如下

在这里插入图片描述

代码:
public class Main3 {
    public static void main(String[] args) {
        int[] arr={5,4,3,2,1};
        BubbleSort(arr,5);
    }
    private static void BubbleSort(int A[], int n) {
        for (int i = 0; i<n-1; i++) {          //每次都将第一个元素与后面元素作比较 比较n-1次
            boolean flag = false;
            int t=n;
            for (int j = 1; j <t;j++ ) {
                if (A[j-1]>A[j]) {          //若为逆序 交换
                    int temp = A[j];
                    A[j]=A[j-1];
                    A[j-1]= temp;
                    flag = true;
                }
                if (flag==false)            //本趟遍历没有发现交换,说明表已经有序
                    return;
            }
            t--;
            //输出排序过程
            for (int m = 0; m <n; m++)
                System.out.print(A[m] + " ");
            System.out.println();
        }
    }
}
代码改进:

巧妙的借助i的变化,从后往前比较,选出最小的放在第一位。

代码
public class Main3 {
    public static void main(String[] args) {
        int[] arr={5,4,3,2,1};
        BubbleSort(arr,5);
    }
    private static void BubbleSort(int A[], int n) {
        for (int i = 0; i<n-1; i++) {          //每次都将第一个元素与后面元素作比较 比较n-1次
            boolean flag = false;
            int t=n;
            for (int j = n-1; j >i;j-- ) {
                if (A[j-1]>A[j]) {          //若为逆序 交换
                    int temp = A[j];
                    A[j]=A[j-1];
                    A[j-1]= temp;
                    flag = true;
                }
                if (flag==false)            //本趟遍历没有发现交换,说明表已经有序
                    return;
            }
            //输出排序过程
            for (int m = 0; m <n; m++)
                System.out.print(A[m] + " ");
            System.out.println();
        }
    }
}
截图

在这里插入图片描述

冒泡排序算法的性能分析
空间效率

仅使用常数个辅助单元,因而空间复杂度为O(1)

时间效率

最坏和平均时间复杂度均为O(n^2)

稳定性

稳定

适用性

冒泡排序算法适用于线性表为顺序存储的情况,同时也适用于链式存储的情况

(2)快速排序

基本思想

快速排序的基本思想是基于分治法的:
1、选出一个key,一般是最左边或是最右边的。
2、定义一个begin和一个end,begin从左向右走,end从右向左走。(需要注意的是:若选择最左边的数据作为key,则需要end先走;若选择最右边的数据作为key,则需要bengin先走)。
3、在走的过程中,若end遇到小于key的数,则停下,begin开始走,直到begin遇到一个大于key的数时,将begin和right的内容交换,end再次开始走,如此进行下去,直到begin和end最终相遇,此时将相遇点的内容与key交换即可。(选取最左边的值作为key)
4.此时key的左边都是小于key的数,key的右边都是大于key的数
5.将key的左序列和右序列再次进行这种单趟排序,如此反复操作下去,直到左右序列只有一个数据,或是左右序列不存在时,便停止操作,此时此部分已有序

单趟动图如下

在这里插入图片描述

代码如下
public class Main4 {
    public static void main(String[] args) {
        int[] arr={5,4,3,2,1};
        QuickSort(arr,0,4);
        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i] + " ");
        }
    }
    private static void QuickSort(int A[], int low,int high) {
        if(low<high){
            int pivotpos=Partition(A,low,high);
            QuickSort(A,low,pivotpos-1);
            QuickSort(A,pivotpos+1,high);
        }
    }

    private static int Partition(int[] A, int low, int high) {
        int 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;                                              //返回枢轴元素的最终位置
    }
}
空间复杂度

最好时为O(log n) 最坏为O(n) 平均为O(log n)

时间复杂度

在这里插入图片描述

稳定性

快速排序是一种不稳定的排序算法
快速排序是所有内部排序算法中平均性能最优的排序算法

快速排序的过程类似于二叉树其高度为logN,每层约有N个数,如下图所示:在这里插入图片描述

(四)选择排序

(1)简单选择排序

思路

每次从待排序列中选出一个最小值,然后放在序列的起始位置,直到全部待排数据排完即可。

动图如下

在这里插入图片描述

代码
public class Main5 {
    public static void main(String[] args) {
        int[] arr={5,4,3,2,1};
        SelectSort(arr,5);
        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i] + " ");
        }
    }
    private static void SelectSort(int A[], int n) {
        for (int i = 0; i < n-1; i++) {
            int min=i;
            for (int j = i+1; j < n; j++)
                if (A[j]<A[min])
                    min=j;
            if(min!=i){
                int temp= A[min];
                A[min]=A[i];
                A[i]=temp;
            }
        }
    }
}
空间效率

仅使用常数个辅助单元,因而空间复杂度为O(1)

时间效率

时间复杂度为O(n^2)

稳定性

不稳定

(2)堆排序

堆的定义

n个关键字序列L[1…n]称为堆
大根堆:满足L(i) >= L(2i)且L(i)>=(2i+1) (1=<i <=[n/2])
在这里插入图片描述

小跟堆:满足L(i) <= L(2i)且L(i)<=(2i+1) (1=<i <=[n/2])
在这里插入图片描述

思路

输出堆顶元素,然后将堆底元素移到堆顶。从根节点开始,向下构造堆

主要解决两个问题:1.如何将无序序列构造成初始堆 2.输出堆顶元素后,如何将剩余元素调整成新的堆

构造初始堆

n个结点的完全二叉树,最后一个结点是[n/2](的下边界)个结点的孩子。将该结点视为子树的根节点与其两个孩子结点比较,将其中的最大者放入子树的根节点(此时为构建大根堆,构建小跟堆则将最小的放在上面)。最后以此对各个结点([n/2]-1~1)为根的子树进行筛选。

例子

在这里插入图片描述

代码
public class Main6 {
    public static void main(String[] args) {
        int A[]={0,5,4,8,9,46};
        HeapSort(A,5);                              //堆排序
    }

    //堆排序
    private static void HeapSort(int A[],int len){
        BuildMaxHeap(A,len);            //构造初始堆
        for (int i= len; i >1;i-- ) {    //n-1趟的交换与构造过程
            System.out.print(A[1]+" ");     //输出堆顶元素
            A[1]=A[i];                      //堆顶元素换成堆底元素
            HeadAdjust(A,1,i-1);    //构造堆
        }
        System.out.println(A[1]+" ");
    }

    //构建初始堆
    private static void BuildMaxHeap(int A[],int len){
        for (int i = len/2; i >0; i--) {
            HeadAdjust(A,i,len);
        }
    }

    //将元素k为根的子树进行调整
    private static void HeadAdjust(int[] A, int k, int len) {
        A[0]=A[k];
        for (int i = 2*k; i <=len; i*=2) {
                if (i<len && A[i]<A[i+1])  i++;         //左右孩子比较 选择较大元素
                if (A[0]>=A[i]) break;                  //堆顶换成最大元素
                else {
                    A[k]=A[i];
                    k=i;
                }
        }
        A[k]=A[0];                                      //被筛选结点的值放入最终位置
    }
}
空间效率

仅使用常数个辅助单元,因而空间复杂度为O(1)

时间效率

时间复杂度为O(nlogn)

稳定性

不稳定

(五)二路归并排序

思想

首先将原始无序序列划分为两个子序列,然后分别对每个子序列递归地进行排序,最后再将有序子序列合并。

归并排序基于分治策略思想。归并排序是简单地进行“分”,重点却在“合”的过程,即对两个有序子序列进行归并的过程:

每次比较子序列头,取出较小的进入结果序列;
其后继续比较两个子序列头,取出较小的进入结果序列,重复上述过程,直到其中一个子序列为空,剩余子序列中的记录就可以直接进入结果序列。

过程

基本做法:首先将初始序列的n个记录看成是n个有序的子序列,每个子序列的长度为1,然后两两归并,得到n/2向上取整(n为奇数时,最后一个序列的长度为1)的有序序列。在此基础上,再对长度为2的有序子序列进行两两归并,得到若干个长度为4的有序子序列。以此类推,直到得到一个长度为n的有序序列为止。
在这里插入图片描述

基本做法

首先将初始序列的n个记录看成是n个有序的子序列,每个子序列的长度为1,然后两两归并,得到n/2向上取整(n为奇数时,最后一个序列的长度为1)的有序序列。在此基础上,再对长度为2的有序子序列进行两两归并,得到若干个长度为4的有序子序列。以此类推,直到得到一个长度为n的有序序列为止。

具体实现

假设待排序列为{4, 2, 6, 1, 3, 8, 5, 9},其二路归并排序的完整过程如下:
在这里插入图片描述
第3次归并后,待排序列长度为n,可以看出,此时待排序列已变为有序序列。

代码

public class Main7 {
    private static int[] B =new int[5];

    public static void main(String[] args) {
        int A[]={5,4,8,9,46};
        MergeSort(A,0,4);
        for (int i = 0; i < A.length; i++) {
            System.out.print(A[i]+" ");
        }
    }
    private static void MergeSort(int[] A,int low,int high ) {
        if (low<high) {
            int mid = (low+high)/2;
            MergeSort(A,low,mid);
            MergeSort(A,mid+1,high);
            Merge(A,low,mid,high);
        }
    }
    private static void Merge(int[] A,int low,int mid,int high){
        int i,j,k;
        for (k = low; k <= high; k++)                                   //复制数组A 此时的B为辅助数组
            B[k]=A[k];
        for (i = low,j=mid+1,k=i; i <= mid &&j <= high; k++){           //此时将A视为空表,比较B的左右两端,将较小者复制到A中
            if (B[i]<=B[j])
                A[k]=B[i++];
            else
                A[k]=B[j++];
        }
        while (i <=mid) A[k++]=B[i++];                              //对表中未检测完的元素进行复制
        while (j <= high) A[k++] = B[j++];
    }
}
结果

在这里插入图片描述

空间效率

空间复杂度为O(n)

时间效率

时间复杂度为O(nlogn)

稳定性

稳定

(六)基数排序

思想

排序算法是一种非比较算法,其原理是将整数按每个位数分别比较。它利用了桶的思想。
按关键字排序分为:最高位优先法和最低位优先法

排序过程

初始数据
在这里插入图片描述

第一趟排序
在这里插入图片描述
第二趟排序
在这里插入图片描述

第三趟排序
在这里插入图片描述
此时数据已经排序好

空间效率

空间复杂度为O(r)(r个队列)

时间效率

时间复杂度为O(d(n+r))(d趟分配和收集)

稳定性

稳定

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

名称是:小小小灵通

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值