前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【数据结构】排序

【数据结构】排序

作者头像
xxxflower
发布2023-04-16 17:47:54
2500
发布2023-04-16 17:47:54
举报
文章被收录于专栏:《数据结构》

1.排序

1.1排序的概念

排序:所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。 稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。

内部排序:数据元素全部放在内存中的排序。 外部排序:数据元素太多不能同时放在内存中,根据排序的要求不能在内外存之间移动数据的排序。

1.2常见的排序算法

2.常见排序算法

2.1插入排序

插入排序的基本思想是:把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列 。实际中我们玩扑克牌时,就用了插入排序的思想。

2.1.1直接插入排序

代码语言:javascript
复制
public static void insertSort(int[] array){
        for (int i = 1; i < array.length; i++) {
            int tmp = array[i];
            int j = i-1;
            for (;j >= 0;j--) {
                if(array[j] > tmp){
                    array[j+1] = array[j];
                }else{
                    break;
                }
            }
            array[j+1] = tmp;
        }
    }

我们来分析一下插入排序时间复杂度: 最好情况下:顺序。O(N) 最坏情况下:逆序。O(N^2)

因此我们可以得出一个结论:当数据量不多,且基本上趋于有序的时候,直接插入排序是非常快的。 空间复杂度:O(1) 稳定性:稳定

2.1.2希尔排序(缩小增量排序)

希尔排序法又称缩小增量法。希尔排序法的基本思想是:先选定一个整数,把待排序文件中所有记录分成个组,所有距离为的记录分在同一组内,并对每一组内的记录进行排序。然后,取,重复上述分组和排序的工作。当到达=1时,所有记录在统一组内排好序。

代码语言:javascript
复制
//希尔排序
    private static void shell(int[] array,int gap){
        for(int i = gap;i < array.length;i++){
            int tmp = array[i];
            int j = i - gap;
            for(;j >= 0;j -= gap){
                if(tmp > array[j]){
                    array[j+gap] = array[j];
                }else{
                    break;
                }
            }
            array[i+gap] = tmp;
        }
    }
    public static void shellSort(int[] array){
        int gap = array.length;
        while(gap > 1){
            gap /= 2;
            shell(array,gap);
        }
    }

希尔排序的时间复杂度是数学上未解决的题,它会随着组别的变化而变化。但是大量的实验的得出局部的结论:O(N^1.3) 稳定性:不稳定。 空间复杂度:O(1)。 希尔排序实际上是对直接插入排序的优化。它使得数字趋于有序化,最后在进行直接插入排序。

2.2选择排序

2.2.1基本思想

每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完 。

方法一:

代码语言:javascript
复制
//选择排序
    public static  void selectSort(int[] array){
        for(int i = 0;i < array.length;i++){
            int minIndex = i;
            for(int j = i+1;j < array.length;j++){
                if(array[j] < array[minIndex]) {
                    minIndex = j;
                }
            }
            //处理两个下标一样的情况,比如第一个数就是最大值,后面找不到比它小的值
            if(i != minIndex){
                swap(array,minIndex,i);
            }
        }

    }
    public static void swap(int[] array,int i,int j){
        int tmp =array[i];
        array[i] = array[j];
        array[j] = tmp;
    }

方法二:

代码语言:javascript
复制
//为了提高效率,可以左右一起找,找最小值放在前面,找最大值放在后面
    public static void selectSort2(int[] array){
        int left = 0;
        int right = array.length -1;
        while(left < right){
            int minIndex = left;
            int maxIndex = right;
            for(int j = left + 1;j <= right;j++){
                if(array[j] < array[minIndex]){
                    minIndex = j;
                }
                if(array[j] > array[maxIndex]){
                    maxIndex = j;
                }
            }
            //将最小值换到前面
            swap(array,minIndex,left);
            //如果max下标正好是Left说明上次已经把最大值从left位置换到了minIndex位置
            if(maxIndex == left){
                maxIndex = minIndex;
            }
            //吧最大值换到后面。
            swap(array,maxIndex,right);
            left++;
            right--;
        }
    }

选择排序的时间复杂度为:O(n^2) 空间复杂度:O(1) 稳定性:不稳定

2.2.3 堆排序

堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。它是通过堆来进行选择数据。需要注意的是排升序要建大堆,排降序建小堆。 堆排序将要排序的数组创建成一个大根堆,再将第一个值和最后一个值进行交换,再将二叉树调整成为大根堆,依次循环排序。

代码语言:javascript
复制
//堆排序
    public static void heapSort(int[] array){
        creatBigHeap(array);
        int end = array.length -1;
        while(end > 0){
            swap(array,0,end);
            shiftDown(array,0,end);
            end--;
        }
    }
    private static void creatBigHeap(int[] array){
        for(int parent = (array.length - 1-1)/2;parent >= 0;parent--){
            shiftDown(array,parent,array.length);
        }
    }
    private static void shiftDown(int[] array,int parent,int len){
        int child = (2* parent) + 1;
        while(child < len){
            if(child+1 < len && array[child] < array[child+1]){
                child++;
            }
            if(array[child] > array[parent]){
                swap(array,child,parent);
                parent = child;
                child = 2 * parent +1;
            }else{
                break;
            }
        }

    }

堆排序的时间复杂度:O(N*logN)

空间复杂度:O(1) 稳定性:不稳定

2.3交换排序

基本思想:所谓交换,就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置,交换排序的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动。

2.3.1冒泡排序

代码语言:javascript
复制
//冒泡排序
    public static void bubbleSort(int[] array){
        for(int i = 0;i < array.length;i++){
            boolean flg = false;
            for(int j = 0; j < array.length - 1-i;j++){
                if(array[j] > array[j+1]){
                    swap(array,j,j+1);
                    flg = true;
                }
            }
            if(flg == false){
                break;
            }
        }
    }

2.3.2快速排序

快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。 1.Hoare排序 hoare排序是从右边开始找一个比key值小的,从左边找一个比key值大的数,两者进行交换,当left==right时,将此数与key交换。以此来排序。

代码语言:javascript
复制
//快速排序
    public static int partitionHoare(int[] array,int left,int right){
        int key = array[left];
        int i = left;
        while(left < right){
            while(left < right && array[right] >= key){
                right--;
            }
            while(left < right && array[left] <= key){
                left++;
            }
            swap(array,left,right);
        }
        swap(array,left,i);
        return left;
    }
  1. 挖坑法 挖坑法就是以第一个数字为基准值,从右边找到一个比基准值大的数字放进基准的位置。再从左边找一个比基准值小的数字放进右边的位置
代码语言:javascript
复制
//挖坑法
    public static int partition2(int[] array,int left,int right){
        int key = array[left];
        while(left < right){
            while(left < right && array[right] >= array[key]){
                right--;
            }
            array[left] = array[right];
            while(left < right && array[left] <= array[right]){
                left++;
            }
            array[right] = array[left];
        }
        array[left] = key;
        return left;
    }
  1. 前后指针法
代码语言:javascript
复制
 //前后指针法
    private static int partition(int[] array, int left, int right) {
        int prev = left ;
        int cur = left+1;
        while (cur <= right) {
            if(array[cur] < array[left] && array[++prev] != array[cur]) {
                swap(array,cur,prev);
            }
            cur++;
        } swap(array,prev,left);
        return prev;
    }

2.3.2快速排序的优化

  1. 三数取中法选key
  2. 递归到小的子区间时,可以考虑使用插入排序
代码语言:javascript
复制
public static void insertSort(int[] array,int left,int right){
        for (int i = 1; i < array.length; i++) {
            int tmp = array[i];
            int j = i-1;
            for (;j >= 0;j--) {
                if(array[j] > tmp){
                    array[j+1] = array[j];
                }else{
                    break;
                }
            }
            array[j+1] = tmp;
        }
    }
public static void quick(int[] array,int start,int end){
        if(start >= end){
            return;
        }
        if(end - start + 1 <= 15){
            insertSort(array,start,end);
            return;
        }
        int index = findMidValIndex(array,start,end);
        swap(array,start,index);
        int key = partition(array,start,end);
        quick(array,start,key-1);
        quick(array,end,key+1);
    }
    public static int findMidValIndex(int[] array,int start,int end){
        int midIndex = (start + end)/2;
        if(array[start] > array[end]){
            if(array[midIndex] > array[start]){
                return start;
            } else if (array[midIndex] < array[end]) {
                return end;
            }else{
                return midIndex;
            }
        }else{
            if(array[midIndex] < array[start]){
                return start;
            } else if (array[midIndex] > array[end]) {
                return end;
            }else{
                return midIndex;
            }
        }
    }

2.3.3快速排序

  1. 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序
  2. 时间复杂度:O(N*logN)
  3. 空间复杂度:O(logN)
  4. 稳定性:不稳定

2.3.5非递归实现快速排序

先找基准 判断基准元素左边和右边是否有两个及以上元素? 如果有,则将首位置和p-1位置放入栈中(注意放入顺序)(处理左边),再将p+1位置和end位置放入栈中(处理右边) 当栈不为空的时候弹出两个元素在判断左边和右边的情况。依次循环

代码语言:javascript
复制
//非递归实现快速排序
    public static void quickSort(int[] array){
        Stack<Integer> stack = new Stack<>();
        int start = 0;
        int end = array.length-1;
        int pivot = partition(array,start,end);
        //1.判断左边是否有两个元素
        if(pivot > start + 1){
            stack.push(start);
            stack.push(pivot-1);
        }
        //2.判断右边是否有两个元素
        if(pivot < end-1){
            stack.push(pivot+1);
            stack.push(end);
        }
        while(!stack.isEmpty()){
            end = stack.pop();
            start = stack.pop();
            pivot = partition(array,start,end);
            //3.判断左边是否有两个元素
            if(pivot > start + 1){
                stack.push(start);
                stack.push(pivot-1);
            }
            //4.判断右边是否有两个元素
            if(pivot < end-1){
                stack.push(pivot+1);
                stack.push(end);
            }
        }
    }

2.4 归并排序

2.4.1递归实现归并排序

归并排序,首先要了解二叉树的基本知识,通过递归将数组分成一个一个的然后在合并。合并的时候可以参考前面写过的例题即两个有序数组合并问题。通过合并可以使数组有序,此时我们建立了新的数组。注意在最后给数组赋值的时候,tmpArr当中的数据是right left之间的有序数据。因此要从右侧开始。

代码语言:javascript
复制
public static void mergeSort1(int[] array){
        mergeSortChild(array,0,array.length-1);
    }
    private static void mergeSortChild(int[] array,int left,int right){
        if(left == right){
            return;
        }
        //拆分
        int mid = (left+right)/2;
        mergeSortChild(array,left,mid);
        mergeSortChild(array,mid+1,right);
        //合并
        merge(array,left,mid,right);
    }
    public static void merge(int[] array,int left,int mid,int right){
        int s1 = left;
        int e1 = mid;
        int s2 = mid +1;
        int e2 = right;
        int[] tmpArr = new int [right - left +1];
        int k = 0;
        while(s1 <= e1 && s2 <= e2){
            if(array[s1] <= array[s2]){
                tmpArr[k++] = array[s1++];
            }else{
                tmpArr[k++] = array[s2++];
            }
        }
        while(s1 <= e1){
            tmpArr[k++] = array[s1++];
        }
        while(s2 <= e2){
            tmpArr[k++] = array[s2++];
        }
        for(int i = 0;i < k;i++){
            array[i+left] = tmpArr[i];
        }
    }

由归并排序的思想可得,其 空间复杂度为:O(N) 时间复杂度为:O(N*logN) 稳定性:稳定 我们学过的稳定排序为:插入,冒泡,归并

2.4.2非递归的归并排序

非递归实现归并排序时,先将数组分成一个一个的,然后通过调整left,mid right来合并数组。

代码语言:javascript
复制
 //非递归实现归并排序
    public static void mergeSort(int[] array){
        int gap = 1;
        while(gap < array.length){
            for(int i = 0;i < array.length;i+=gap){
                int left = i;
                int mid = left + gap-1;
                int right = mid+gap;
                //需要考虑mid和right越界的问题,i在循环中,所以不用考虑i越界的问题
                if(mid >= array.length){
                    mid = array.length -1;
                }
                if(right >= array.length){
                    right = array.length-1;
                }
                merge(array,left,mid,right);
            }
            gap = 2*gap;
        }
    }

3.排序的总结

计数排序

代码语言:javascript
复制
public static void countSort(int[] array) {
        //1、遍历数组 找到 最小值 和最大值  -》 才能确定 计数数组的大小
        int maxVal = array[0];
        int minVal = array[0];
        //O(n)
        for (int i = 0; i < array.length; i++) {
            if(array[i] > maxVal) {
                maxVal = array[i];
            }
            if(array[i] < minVal) {
                minVal = array[i];
            }
        }
        //2、确定计数数组的长度
        int len = maxVal - minVal + 1 ;
        int[] countArr = new int[len];
        //3. 开始遍历 当前数组 统计每个数字出现的次数  O(n)
        for (int i = 0; i < array.length; i++) {
            int val = array[i];
            countArr[val-minVal] ++;//??????????????
        }

        int index = 0;
        //4. 遍历计数数组,看每个下标的值是几,就打印几个下标的数据就好了 O(范围 + n)
        //范围遍历一次,位置上所有的数的个数加起来等于n
        for (int i = 0; i < countArr.length; i++) {
            while (countArr[i] > 0) {
                //不敢打印
                array[index] = i+minVal;//??????????????
                index++;
                countArr[i]--;
            }
        }
    }
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-03-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.排序
    • 1.1排序的概念
      • 1.2常见的排序算法
      • 2.常见排序算法
        • 2.1插入排序
          • 2.1.1直接插入排序
          • 2.1.2希尔排序(缩小增量排序)
        • 2.2选择排序
          • 2.2.1基本思想
          • 2.2.3 堆排序
        • 2.3交换排序
          • 2.3.1冒泡排序
          • 2.3.2快速排序
          • 2.3.2快速排序的优化
          • 2.3.3快速排序
          • 2.3.5非递归实现快速排序
        • 2.4 归并排序
          • 2.4.1递归实现归并排序
          • 2.4.2非递归的归并排序
          • 计数排序
      • 3.排序的总结
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档