⚽冒泡排序
==冒泡排序(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;
}
🏀冒泡排序的特性总结
-
冒泡排序是一种非常容易理解的排序
-
时间复杂度:O(N^2)
-
空间复杂度:O(1)
-
稳定性:稳定
-
什么时候最快
当输入的数据已经是正序时(都已经是正序了,我还要你冒泡排序有何用啊)。 -
什么时候最慢
当输入的数据是反序时(写一个 for 循环反序输出数据不就行了,干嘛要用你冒泡排序呢,我是闲的吗)。
🧭快速排序
快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止
⚽算法思路
📌思路一(Hoare版)
步骤为:
-
选取基准值
-
从数组右->左找到比基准值小于或等于的值的下标
-
从数组右->左找到比基准值大于或等于的值的下标
-
交换这两下标的值
-
继续执行二操作,直到操作2与操作3相遇
-
将基准值放在相遇位置
如下图所示:
📌思路二(挖坑法)
步骤为:
-
选取基准值后,记录下基准值,假设该下标为空,相当于是个“坑”
-
从右->左找小于或等于基准值的值,就将该数放入坑中,然后该下标变为新的坑
-
从左->右找小于或等于基准值的值,就将该数放入坑中,然后该下标变为新的坑
-
回到步骤2继续执行,直到操作2与操作3所找数相同
-
将记录下的基准值放回坑里
图示如下:
📌思路三(前后指针)
步骤及其动图如下:
🎨代码实现:
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;
}
🎡快速排序特性总结
-
快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序
-
时间复杂度:O(N*logN)
-
空间复杂度:O(logN)
-
稳定性:不稳定
🥎归并排序
⚽基本思想
归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and
Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。 归并排序核心步骤:
🏀算法步骤
-
申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列;
-
设定两个指针,最初位置分别为两个已经排序序列的起始位置;
-
比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置;
-
重复步骤 3 直到某一指针达到序列尾;
-
将另一序列剩下的所有元素直接复制到合并序列尾。
🛫代码实现:
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];
}
}
🛬归并排序特性总结
-
归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。
-
时间复杂度:O(N*logN)
-
空间复杂度:O(N)
-
稳定性:稳定
🌴海量数据的排序问题
外部排序:排序过程需要在磁盘等外部存储进行的排序
前提:内存只有 1G,需要排序的数据有 100G
因为内存中因为无法把所有数据全部放下,所以需要外部排序,而归并排序是最常用的外部排序
-
先把文件切分成 200 份,每个 512 M
-
分别对 512 M 排序,因为内存已经可以放的下,所以任意排序方式都可以
-
进行 2路归并,同时对 200 份有序文件做归并过程,最终结果就有序了
🐱🏍排序算法复杂度及稳定性分析
文章来源:https://www.toymoban.com/news/detail-709377.html
⭕总结
关于《【数据结构】 七大排序详解(贰)——冒泡排序、快速排序、归并排序》就讲解到这儿,感谢大家的支持,欢迎各位留言交流以及批评指正,如果文章对您有帮助或者觉得作者写的还不错可以点一下关注,点赞,收藏支持一下!文章来源地址https://www.toymoban.com/news/detail-709377.html
到了这里,关于【数据结构】 七大排序详解(贰)——冒泡排序、快速排序、归并排序的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!