【数据结构】 七大排序详解(贰)——冒泡排序、快速排序、归并排序

简介: 【数据结构】 七大排序详解(贰)——冒泡排序、快速排序、归并排序

冒泡排序

==冒泡排序(Bubble Sort)==也是一种简单直观的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢"浮"到数列的顶端。

⚾算法步骤

比较相邻的元素。如果第一个比第二个大,就交换他们两个。

对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。

针对所有的元素重复以上的步骤,除了最后一个。

持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

🎨算法优化

冒泡排序还有一种优化算法,就是立一个 flag,当在一趟序列遍历中元素没有发生交换,则证明该序列已经有序

直接返回就好

🥎代码实现:

public  int[] bubbleSort(int[] arr) {
        int[] array = Arrays.copyOf(arr,arr.length);
        for(int i = 1;i < array.length ; i ++) {
            Boolean a = true;
            for(int j = 0; j < array.length - i; j++) {
                if(array[j] > array[j + 1]) {
                    swap(array,j,j+1);
                    a = false;
                }
            }
            if(a) {
                return array;
            }
        }
        return array;
    }
    private void swap (int[] arr,int m,int n) {
        int tmp = arr[m];
        arr[m] = arr[n];
        arr[n] = tmp;
    }

🏀冒泡排序的特性总结

  1. 冒泡排序是一种非常容易理解的排序
  2. 时间复杂度:O(N^2)
  3. 空间复杂度:O(1)
  4. 稳定性:稳定
  5. 什么时候最快
    当输入的数据已经是正序时(都已经是正序了,我还要你冒泡排序有何用啊)。
  6. 什么时候最慢
    当输入的数据是反序时(写一个 for 循环反序输出数据不就行了,干嘛要用你冒泡排序呢,我是闲的吗)。

🧭快速排序

快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止

⚽算法思路

📌思路一(Hoare版)

步骤为:

  1. 选取基准值
  2. 从数组右->左找到比基准值小于或等于的值的下标
  3. 从数组右->左找到比基准值大于或等于的值的下标
  4. 交换这两下标的值
  5. 继续执行二操作,直到操作2与操作3相遇
  6. 将基准值放在相遇位置

如下图所示:

📌思路二(挖坑法)

步骤为:

  1. 选取基准值后,记录下基准值,假设该下标为空,相当于是个“坑”
  2. 从右->左找小于或等于基准值的值,就将该数放入坑中,然后该下标变为新的坑
  3. 从左->右找小于或等于基准值的值,就将该数放入坑中,然后该下标变为新的坑
  4. 回到步骤2继续执行,直到操作2与操作3所找数相同
  5. 将记录下的基准值放回坑里

图示如下:

📌思路三(前后指针)

步骤及其动图如下:

🎨代码实现:

public int[] quickSort(int[] array) {
        int[] arr = Arrays.copyOf(array,array.length);
        int left = 0;
        int right = arr.length - 1;
        quick(arr,left,right);
        return arr;
    }
    private void quick(int[] array,int begin,int end) {
        if(begin >= end) {
            return;
        }
        int centre = partition1(array,begin,end);
        //int centre = partition2(array,begin,end);
        //int centre = partition3(array,begin,end);
        quick(array,centre + 1,end);
        quick(array,begin,centre - 1);
    }
    //挖坑法
    private  int partition1(int[] array,int left,int right) {
        int tmp = array[left];
        while (left < right) {
            while (left< right && array[right] >= tmp) {
                right--;
            }
            array[left] = array[right];
            while (left< right && array[left] <= tmp) {
                left++;
            }
            array[right] = array[left];
        }
        array[left] = tmp;
        return left;
    }
    //Hoare版
    private  int partition2(int[] array,int left,int right) {
        int tmp = array[left];
        int i = left;
        while (left < right) {
            while (left< right && array[right] >= tmp) {
                right--;
            }
            while (left< right && array[left] <= tmp) {
                left++;
            }
            swap(array,left,right);
        }
        swap(array,left,i);
        return left;
    }
    //前后指针法
    private int partition3(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;
    }
    private void swap (int[] arr,int m,int n) {
        int tmp = arr[m];
        arr[m] = arr[n];
        arr[n] = tmp;
    }

🌳快速排序优化

📌规模较小时的优化

每次递归的时候,数据都是再慢慢变成有序的

当数据量少且趋于有序的时候,我们可以直接使用插入排序进行优化

private void quick(int[] array,int begin,int end) {
        if(begin >= end) {
            return;
        }
        if(end - begin < 20) {
            //插入排序
            //......
            return;
        }
        int centre = partition1(array,begin,end);
        //int centre = partition2(array,begin,end);
        //int centre = partition3(array,begin,end);
        quick(array,centre + 1,end);
        quick(array,begin,centre - 1);
    }

📌三数取中法

如果在选取基数时我们发现如果基数一边总是没有数,代码的执行次数会增加很多

所以我们的解决方法为:

选取数组第一个数、中间的数、和最后一个数,进行比较

三数中间的数作为每次的基数

寻找中间数代码如下:

private  int midThree(int[] array,int left,int right) {
        int mid = (left + right) / 2;
        //6  8
        if (array[left] < array[right]) {
            if (array[mid] < array[left]) {
                return left;
            } else if (array[mid] > array[right]) {
                return right;
            } else {
                return mid;
            }
        } else {
            //array[left] > array[right]
            if (array[mid] < array[right]) {
                return right;
            } else if (array[mid] > array[left]) {
                return left;
            } else {
                return mid;
            }
        }
    }

使用如下:

private  int partition1(int[] array,int left,int right) {
        int tmp = midThree(array,left,right);
        while (left < right) {
            while (left< right && array[right] >= tmp) {
                right--;
            }
            array[left] = array[right];
            while (left< right && array[left] <= tmp) {
                left++;
            }
            array[right] = array[left];
        }
        array[left] = tmp;
        return left;
    }
    //Hoare版
    private  int partition2(int[] array,int left,int right) {
        int tmp = midThree(array,left,right);
        int i = left;
        while (left < right) {
            while (left< right && array[right] >= tmp) {
                right--;
            }
            while (left< right && array[left] <= tmp) {
                left++;
            }
            swap(array,left,right);
        }
        swap(array,left,i);
        return left;
    }

🏀快速排序非递归实现

实现思路:

  • 建立一个栈
  • 先让一组数据的起点入栈
  • 再让一组数据的终点出栈

  • 然后两次出栈,分别作为该数据的起点与终点
  • 然后经过我们上面所写的方法进行排序后
  • 再将两组数据进行入栈

  • 以此循环直到栈为空

🚩代码实现:

//快速排序递归实现
    public int[] quickSortPlus(int[] array) {
        int[] arr = Arrays.copyOf(array,array.length);
        Deque<Integer> stack = new LinkedList<>();
        int left = 0;
        int right = array.length-1;
        int pivot = 0;
        stack.push(left);
        stack.push(right);
        while (!stack.isEmpty()) {
            right= stack.pop();
            left = stack.pop();
            pivot = partition(arr,left,right);
            if(pivot > left+1) {
                stack.push(left);
                stack.push(pivot-1);
            }
            if(pivot < right-1) {
                stack.push(pivot+1);
                stack.push(right);
            }
        }
        return arr;
    }
    private  int partition(int[] array,int left,int right) {
        int tmp = array[left];
        while (left < right) {
            while (left< right && array[right] >= tmp) {
                right--;
            }
            array[left] = array[right];
            while (left< right && array[left] <= tmp) {
                left++;
            }
            array[right] = array[left];
        }
        array[left] = tmp;
        return left;
    }

🎡快速排序特性总结

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

  3. 空间复杂度:O(logN)
  4. 稳定性:不稳定

🥎归并排序

⚽基本思想

归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and

Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。 归并排序核心步骤:

🏀算法步骤

  1. 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列;
  2. 设定两个指针,最初位置分别为两个已经排序序列的起始位置;
  3. 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置;
  4. 重复步骤 3 直到某一指针达到序列尾;
  5. 将另一序列剩下的所有元素直接复制到合并序列尾。

🛫代码实现:

public  void mergeSort1(int[] array) {
        mergeSortFunc(array,0,array.length-1);
    }
    private  void mergeSortFunc(int[] array,int left,int right) {
        if(left >= right) {
            return;
        }
        int mid = (left+right) / 2;
        mergeSortFunc(array,left,mid);
        mergeSortFunc(array,mid+1,right);
        merge(array,left,right,mid);
    }
    private  void merge(int[] array,int start,int end,int mid) {
        int s1 = start;
        //int e1 = mid;
        int s2 = mid+1;
        //int e2 = end;
        int[] tmp = new int[end-start+1];
        int k = 0;//tmp数组的下标
        while (s1 <= mid && s2 <= end) {
            if(array[s1] <= array[s2]) {
                tmp[k++] = array[s1++];
            }else {
                tmp[k++] = array[s2++];
            }
        }
        while (s1 <= mid) {
            tmp[k++] = array[s1++];
        }
        while (s2 <= end) {
            tmp[k++] = array[s2++];
        }
        for (int i = 0; i < tmp.length; i++) {
            array[i+start] = tmp[i];
        }
  }

😎递归实现归并排序

public static void mergeSort(int[] array) {
        int gap = 1;
        while (gap < array.length) {
            // i += gap * 2 当前gap组的时候,去排序下一组
            for (int i = 0; i < array.length; i += gap * 2) {
                int left = i;
                int mid = left+gap-1;//有可能会越界
                if(mid >= array.length) {
                    mid = array.length-1;
                }
                int right = mid+gap;//有可能会越界
                if(right>= array.length) {
                    right = array.length-1;
                }
                merge(array,left,right,mid);
            }
            //当前为2组有序  下次变成4组有序
            gap *= 2;
        }
    }
    private  void merge(int[] array,int start,int end,int mid) {
        int s1 = start;
        //int e1 = mid;
        int s2 = mid+1;
        //int e2 = end;
        int[] tmp = new int[end-start+1];
        int k = 0;//tmp数组的下标
        while (s1 <= mid && s2 <= end) {
            if(array[s1] <= array[s2]) {
                tmp[k++] = array[s1++];
            }else {
                tmp[k++] = array[s2++];
            }
        }
        while (s1 <= mid) {
            tmp[k++] = array[s1++];
        }
        while (s2 <= end) {
            tmp[k++] = array[s2++];
        }
        for (int i = 0; i < tmp.length; i++) {
            array[i+start] = tmp[i];
        }
  }

🛬归并排序特性总结

  1. 归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。
  2. 时间复杂度:O(N*logN)
  3. 空间复杂度:O(N)
  4. 稳定性:稳定

🌴海量数据的排序问题

外部排序:排序过程需要在磁盘等外部存储进行的排序

前提:内存只有 1G,需要排序的数据有 100G

因为内存中因为无法把所有数据全部放下,所以需要外部排序,而归并排序是最常用的外部排序

  1. 先把文件切分成 200 份,每个 512 M
  2. 分别对 512 M 排序,因为内存已经可以放的下,所以任意排序方式都可以
  3. 进行 2路归并,同时对 200 份有序文件做归并过程,最终结果就有序了

🐱‍🏍排序算法复杂度及稳定性分析

⭕总结

关于《【数据结构】 七大排序详解(贰)——冒泡排序、快速排序、归并排序》就讲解到这儿,感谢大家的支持,欢迎各位留言交流以及批评指正,如果文章对您有帮助或者觉得作者写的还不错可以点一下关注,点赞,收藏支持一下!

相关文章
|
2天前
【初阶数据结构】打破递归束缚:掌握非递归版快速排序与归并排序
【初阶数据结构】打破递归束缚:掌握非递归版快速排序与归并排序
|
2天前
|
搜索推荐 C++
【初阶数据结构】深度解析七大常见排序|掌握底层逻辑与原理(一)
【初阶数据结构】深度解析七大常见排序|掌握底层逻辑与原理
|
2天前
|
搜索推荐 索引
【初阶数据结构】深度解析七大常见排序|掌握底层逻辑与原理(二)
【初阶数据结构】深度解析七大常见排序|掌握底层逻辑与原理
|
2天前
|
人工智能 搜索推荐 算法
【初阶数据结构】深度解析七大常见排序|掌握底层逻辑与原理(三)
【初阶数据结构】深度解析七大常见排序|掌握底层逻辑与原理
|
2天前
|
存储 算法 搜索推荐
探索常见数据结构:数组、链表、栈、队列、树和图
探索常见数据结构:数组、链表、栈、队列、树和图
76 64
|
10天前
|
算法 安全 测试技术
golang 栈数据结构的实现和应用
本文详细介绍了“栈”这一数据结构的特点,并用Golang实现栈。栈是一种FILO(First In Last Out,即先进后出或后进先出)的数据结构。文章展示了如何用slice和链表来实现栈,并通过golang benchmark测试了二者的性能差异。此外,还提供了几个使用栈结构解决的实际算法问题示例,如有效的括号匹配等。
golang 栈数据结构的实现和应用
|
2天前
|
Go
数据结构之 - 深入了解栈数据结构
数据结构之 - 深入了解栈数据结构
11 5
|
11天前
01_设计一个有getMin功能的栈
01_设计一个有getMin功能的栈
|
11天前
|
前端开发
07_用队列实现栈
07_用队列实现栈
|
11天前
06_用栈来求解汉诺塔问题
06_用栈来求解汉诺塔问题