600字范文,内容丰富有趣,生活中的好帮手!
600字范文 > 十大经典排序算法(冒泡 选择 插入 希尔 快排..........)

十大经典排序算法(冒泡 选择 插入 希尔 快排..........)

时间:2022-04-04 07:36:50

相关推荐

十大经典排序算法(冒泡 选择 插入 希尔 快排..........)

十大经典排序算法

1. 相关术语介绍2. 排序算法分类以及复杂度3.冒泡排序4 选择排序4.1 直接选择排序4.2 双向选择排序5. 插入排序5.1 直接插入排序5.2 折半插入排序6.希尔排序7.归并排序(递归+非递归)8.快速排序(递归+非递归)8.1 基础快速排序(递归+非递归)8.2 二路快速排序8.3 三路快速排序

1. 相关术语介绍

稳定排序:如果a原本在b前面,而a=b,排序之后a仍然在b的前面;

非稳定排序:如果a原本在b的前面,而a=b,排序之后a可能会出现在b的后面;

内排序:所有排序操作都在内存中完成;

外排序:由于数据太大,因此把数据放在磁盘中,而排序通过磁盘和内存的数据传输才能进行;

时间复杂度:排序所耗费的时间;

空间复杂度:排序所耗费的内存;

比较类排序:通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),因此也称为非线性时间比较类排序

非比较类排序:不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界,以线性时间运行,因此也称为线性时间非比较类排序

2. 排序算法分类以及复杂度

排序算法分类

十大排序算法主要可以分为两大类:1.比较类排序 2.非比较类排序,下面是排序算法的分类

不同排序算法的复杂度以及稳定性

3.冒泡排序

下面对数据从小到大排序

思想:每次寻找一组数据中最大的元素,两两比较,找到后放到数据的最后一位。n个数据第一次两两比较n-1次。

第二次比较n-2次,比较的时候如果前一个元素比第二个元素大则相互交换。

总共两层循环:第一层代表需对比几轮;第二层代表需要两两对比多少次

以下面的图举例

15个数,第一轮两两对比49次找出15个数中最大的数50

第二轮两两对比48次找到次大数48

以此类推…

那总共需要多少轮呢?

15个数,需要14轮

每轮需要两两对比多少次呢?

很明显,每轮两两对比的次数不固定

第一轮两两对比14次找到最大数

第二轮需要对比13次,因为不用和最后一个数对比

C语言代码

#define _CRT_SECURE_NO_WARNINGS#include<stdio.h>int BubbleSort(int arr[],int len){int temp = 0;for (int i = 0; i < len-1; i++){for (int j = 0; j < len-1-i; j++){if (arr[j] > arr[j + 1]){temp = arr[j];arr[j] = arr[j + 1];arr[j + 1] = temp;}}}for (int i = 0; i < len; i++){printf("%d ", arr[i]);}}int main(){int arr[] = {3,44,38,5,47,15,36,26,27,2,46,4,19,50,48 };int len = sizeof(arr) / sizeof(arr[0]);BubbleSort(arr,len);}

java代码

package sort;import java.util.Arrays;public class SelfSevenSort {// 冒泡排序// 在无序区间,通过相邻数的比较,将最大的数冒泡到无序区间的最后,持续这个过程,直到数组整体有序public static void BubbleSort(int[] arr){for (int i = 0; i < arr.length-1; i++) {for (int j = 0; j < arr.length-1-i; j++) {if(arr[j]>arr[j+1]){int tmp=arr[j];arr[j]=arr[j+1];arr[j+1]=tmp;}}}}public static void main(String[] args) {int[] arr={2,1,4,6,3,7,9,0};BubbleSort(arr);System.out.println(Arrays.toString(arr));}}

4 选择排序

4.1 直接选择排序

选择排序:每一次从无序区间选出最大(或最小)的一个元素,存放在无序区间的最后(或最前),直到全部待排序的数据元素排完 。

每轮都找到一组数据中最小的数依次放在数组前面。

与冒泡排序的不同:冒泡排序在找数的时候,每次比较都需要进行数据交换,而选择排序是两两对比 记录数据 的索引,找到数据后只进行一次交换。

以对{ 3,44,38,5,47,15,36,26,27,2,46,4,19,50,48 }由小到大排列为例

C语言代码

#define _CRT_SECURE_NO_WARNINGS#include<stdio.h>void selectionSort(int arr[], int len){int temp = 0;for (int i = 0; i < len-1; i++){int index = i;for (int j = i+1; j < len; j++){if (arr[j] < arr[index]){index = j;}}temp = arr[i];arr[i] = arr[index];arr[index] = temp;}for (int i = 0; i < len; i++){printf("%d ", arr[i]);}}int main(){int arr[] = {3,44,38,5,47,15,36,26,27,2,46,4,19,50,48 };int len = sizeof(arr) / sizeof(arr[0]);selectionSort(arr, len);}

java代码

package sort;import java.util.Arrays;public class SelfSevenSort {// 选择排序// 每一次从无序区间选出最大(或最小)的一个元素,存放在无序区间的最后(或最前),// 直到全部待排序的数据元素排完。public static void SelectionSort(int[] arr){// 最开始,无序区间[0...n] 有序区间[]// 当无序区间只剩下一个元素时,整个集合已经有序for (int i = 0; i < arr.length-1; i++) {int min=i;for (int j = i+1; j < arr.length; j++) {if (arr[j]<arr[min]){min=j;}}int tmp=arr[i];arr[i]=arr[min];arr[min]=tmp;}}public static void main(String[] args) {int [] arr1={3,44,38,5,47,15,36,26,27,2,46,4,19,50,48};SelectionSort(arr1);System.out.println(Arrays.toString(arr1));}}

4.2 双向选择排序

双向选择排序:每一次从无序区间选出最小 + 最大的元素,存放在无序区间的最前和最后,直到全部待排序的数据元素排完 。

low:无序区间起始位置

high:无序区间终止位置

min:最小值索引

max:最大值索引

package sort;import java.util.Arrays;public class SelfSevenSort {// 双向选择排序// 双向选择排序:每一次从无序区间选出最小 + 最大的元素,存放在无序区间的最前和最后,直到全部待排序的数据元素排完 。// low:无序区间起始位置// high:无序区间终止位置// min:最小值索引// max:最大值索引public static void SelectionSortOP(int[] arr){int low=0;int high=arr.length-1;// low = high,无序区间只剩下一个元素,整个数组已经有序while(low<=high){// 定义max min为无序区间的起始位置索引int max=low;int min=low;// 无序区间for (int i = low+1; i <= high; i++) {if(arr[i]<arr[min]){min=i;}if(arr[i]>arr[max]){max=i;}}int tmp=arr[low];arr[low]=arr[min];arr[min]=tmp;// 如果最大值对应的索引刚好是无序区间的起始位置,经过上面的调整,最大值被调整到了arr[min]位置if (max==low){max=min;}int tmp1=arr[high];arr[high]=arr[max];arr[max]=tmp1;high--;low++;}}public static void main(String[] args) {int [] arr1={3,44,38,5,47,15,36,26,27,2,46,4,19,50,48};SelectionSortOP(arr1);System.out.println(Arrays.toString(arr1));}}

5. 插入排序

5.1 直接插入排序

整个区间被分为

有序区间无序区间

每次选择无序区间的第一个元素,在有序区间内选择合适的位置插入

插入排序和选择排序最大的区别:当插入排序当前遍历的元素>前驱元素时,此时可以提前结束内层循环

极端情况,当集合是一个完全有序的集合时,插入排序内层循环一次都不走,插入排序为O(n) 插入排序是许多高阶排序算法的优化基础

package sort;import java.util.Arrays;public class SelfSevenSort {// 插入排序// 整个区间被分为//1. 有序区间//2. 无序区间//每次选择无序区间的第一个元素,在有序区间内选择合适的位置插入public static void insrtSort(int[] arr){// 有序区间 已排序区间[0,i)for (int i = 1; i < arr.length; i++) {// 待排序区间[i...n]// 待排序区间的第一个元素arr[i]// 从待排序区间的第一个元素向前看,找到合适的插入位置for (int j = i; j >0 ; j--) {// arr[j - 1]已排序区间的最后一个元素// 相等我们也不交换,保证稳定性// 此时说明arr[j] > 已排序区间的最大值,arr[j]已经有序了~~直接下次循环if (arr[j]>=arr[j-1]){break;}else{int tmp=arr[j];arr[j]=arr[j-1];arr[j-1]=tmp;}}}}public static void main(String[] args) {int[] arr={2,1,4,6,3,7,9,0};BubbleSort(arr);System.out.println(Arrays.toString(arr));int [] arr1={3,44,38,5,47,15,36,26,27,2,46,4,19,50,48};insrtSort(arr1);System.out.println(Arrays.toString(arr1));}}

5.2 折半插入排序

在有序区间用二分法选择数据应该插入的位置时,因为区间的有序性,可以利用折半查找的思想

package sort;import java.util.Arrays;public class SelfSevenSort {// 折半插入排序// 在有序区间用二分法选择数据应该插入的位置时,因为区间的有序性,可以利用折半查找的思想public static void inserttSortBS(int[] arr) {for (int i = 1; i < arr.length; i++) {int right = i;int left = 0;int val = arr[i];while (left < right) {int mid = left + ((right - left) >> 1);if (val < arr[mid]) {right = mid;} else {// val>=arr[mid]left = mid + 1;}}for (int j = i; j > left; j--) {arr[j] = arr[j - 1];}// left就是val插入的位置arr[left] = val;}}public static void main(String[] args) {int[] arr = {2, 1, 4, 6, 3, 7, 9, 0};BubbleSort(arr);System.out.println(Arrays.toString(arr));int[] arr1 = {3, 44, 38, 5, 47, 15, 36, 26, 27, 2, 46, 4, 19, 50, 48};inserttSortBS(arr1);System.out.println(Arrays.toString(arr1));}}

6.希尔排序

希尔排序法又称缩小增量法

希尔排序法的基本思想是:先选定一个整数gap(gap一般为数组长度的一半或1/3),把待排序数组以gap为间隔分成个组,个组之间内部使用插入排序,排序之后,再将gap/=2或gap/=3,重复上述流程,直到gap=1,此时数组已经近乎有序,利用插入排序对近乎有序的数组进行调整。

例如

arr=[9,1,2,5,7,4,8,6,3,5] 数组长度为10 ,gap取5

将待排序数组以5为间隔分成5组[9,4] [1,8] [2,6] [5,3] [7,5]

5组内部分别利用插入排序进行调整

调整后[4,9] [1,8] [2,6] [3,5] [5,7]

此时arr=[4,1,2,3,5,9,8,6,5,7]

gap/2=2

package sort;import java.util.Arrays;public class SelfSevenSort {public static void swap(int[] arr,int i,int j){int tmp=arr[i];arr[i]=arr[j];arr[j]=tmp;}// 希尔排序// ap,gap一般为数组长度的一半或1/3,将待排序的数组先按照//gap分组,不同组之间内部使用插入排序,排序之后,再将//gap/=2或gap/=3,重复上述流程,直到gap=1,此时数组已经//近乎有序,利用插入排序对近乎有序的数组进行调整。public static void shellSort(int[] arr){int gap=arr.length>>1;while(gap>1){// 当gap>1时,对arr以gap为间隔进行分组,组内进行插入排序insertionSortBygap(arr,gap);gap = gap>>1;}// 此时gap==1,数组接近有序,对此时的arr进行插入排序insrtSort(arr);}// 对arr以gap为间隔进行分组,组内进行插入排序public static void insertionSortBygap(int[] arr,int gap){for (int i = gap; i < arr.length; i++) {for (int j = i; j - gap >= 0 ; j=j-gap) {if (arr[j]>arr[j-gap]){break;}if(arr[j]<arr[j-gap]){swap(arr,j,j-gap);}}}}public static void main(String[] args) {int[] arr2={9,1,5,8,3,7,4,6,2};shellSort(arr2);System.out.println(Arrays.toString(arr2));}}

7.归并排序(递归+非递归)

归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子

序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。

将原数组不断拆分,一直拆到每个子数组只有一个元素时,第一个阶段结束,归而为1的过程

归并排序,递归写法

import java.util.ArrayDeque;import java.util.Arrays;import java.util.Deque;import java.util.concurrent.ThreadLocalRandom;public class SelfSevenSort {//交换两个元素public static void swap(int[] arr,int i,int j){int tmp=arr[i];arr[i]=arr[j];arr[j]=tmp;}// 归并排序public static void mergeSort(int[] arr) {mergeSortInternal(arr,0,arr.length - 1);}// 在arr[l...r]进行归并排序,整个arr经过函数后就是一个已经有序的数组private static void mergeSortInternal(int[] arr, int l, int r) {if (l >= r) {// 当前数组只剩下一个元素,归过程就结束了return;}int mid = l + ((r - l) >> 1);// 将原数组拆分为左右两个小区间,分别递归进行归并排序mergeSortInternal(arr,l,mid);mergeSortInternal(arr,mid + 1,r);// merge 拆分结束,合并两个数组merge(arr,l,mid,r);}// 合并两个子数组arr[l..mid] 和 arr[mid + 1...r]// 为一个大的有序数组arr[l...r]private static void merge(int[] arr, int l, int mid, int r) {// 先创建一个新的临时数组auxint[] aux = new int[r - l + 1];// 将arr元素值拷贝到aux上for (int i = 0; i < aux.length; i++) {// 每次区间起始位置不同 因此 aux[i]对应arr[i+l]aux[i] = arr[i + l];}// 合并两个区间,i就是左侧小数组的开始索引,第一个区间int i = l;// j就是右侧小数组的开始索引int j = mid + 1;int count=0;// k表示当前正在合并的原数组的索引下标for (int k = l; k <= r; k++) {if (i > mid) {// 左侧区间已经被处理完毕,只需要将右侧区间的值拷贝原数组即可arr[k] = aux[j - l];j ++;}else if (j > r) {// 右侧区间已经被处理完毕,只需要将左侧区间的值拷贝到原数组即可arr[k] = aux[i - l];count=i ++;}else if (aux[i - l] <= aux[j - l]) {// 此时左侧区间的元素值较小,相等元素放在左区间,保证稳定性arr[k] = aux[i - l];i ++;}else {// 右侧区间的元素值较小arr[k] = aux[j - l];count++;j ++;}}}// 归并排序,小区间使用插入排序public static void insertionSort(int[] arr,int l,int r){for (int i = l; i < arr.length; i++) {for (int j = i; j-1>0; j--) {if (arr[j]>arr[j-1]){break;}if(arr[j]<arr[j-1]){swap(arr,j,j-1);}}}}public static void main(String[] args) {int[] arr2={9,1,5,8,3,7,4,6,2};mergeSort(arr2);System.out.println(Arrays.toString(arr2));}}

非递归代码

/*** 归并排序的迭代写法* @param arr*/public static void mergeSortNonRecursion(int[] arr) {// 最外层循环表示每次合并的子数组的元素个数for (int sz = 1; sz <= arr.length; sz += sz) {// 内层循环的变量i表示每次合并的开始索引// i + sz 就是右区间的开始索引,i + sz < arr.length说明还存在右区间for (int i = 0; i + sz < arr.length ; i += sz + sz) {merge(arr,i,i + sz - 1,Math.min(i + sz + sz - 1,arr.length - 1));}}}

8.快速排序(递归+非递归)

8.1 基础快速排序(递归+非递归)

快速排序的基本思想:通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。

从待排序区间选择一个数,作为基准值(pivot);Partition: 遍历整个待排序区间,将比基准值小的(可以包含相等的)放到基准值的左边,将比基准值大的(可以包含相等的)放到基准值的右边;采用分治思想,对左右两个小区间按照同样的方式处理,直到小区间的长度 == 1,代表已经有序,或者小区间的长度 == 0,代表没有数据。

思路:

基准值选取最左侧的元素,对整个数组进行分区,橙色为小于基准值的区间,紫色为大于基准值的区间,e为正在遍历的元素。

橙色区间 arr[l+1,j]

紫色区间arr[j+1,i)

将遍历的元素与基准值进行比较,有两种情况

case1: arr[i] >= v,此时只需要 i++ 就行 紫色区域自动加1

case2: arr[i]<v, 此时将紫色区域第一个大于等于v的元素与当前元素进行交换。swap(arr[j+1],arr[i])

第一次将整个集合扫描完,整个区间被分为三部分

此时交换arr[j]和arr[l],继续在两个区间上重复上述过程。

package sort;import java.util.ArrayDeque;import java.util.Arrays;import java.util.Deque;import java.util.concurrent.ThreadLocalRandom;public class SelfSevenSort {public static void swap(int[] arr,int i,int j){int tmp=arr[i];arr[i]=arr[j];arr[j]=tmp;}/*** 快速排序* @param arr*/public static void quickSort(int[] arr) {quickSortInternal(arr,0,arr.length - 1);}/*** 在arr[l..r]上进行快速排序* @param arr* @param l* @param r*/private static void quickSortInternal(int[] arr, int l, int r) {if (l>r) {return;}// 先获取分区点// 所谓的分区点就是经过分区函数后,某个元素落在了最终的位置// 分区点左侧全都是小于该元素的区间,分区点右侧全都是 >= 该元素的区间int p = partition(arr,l,r);// 重复在左区间和右区间上重复上述流程quickSortInternal(arr,l,p - 1);quickSortInternal(arr,p + 1,r);}/*** 在arr[l..r]上的分区函数,返回分区点的索引* @param arr* @param l* @param r* @return*/private static int partition(int[] arr, int l, int r) {// // 随机在当前数组中选一个数// ThreadLocalRandom random=ThreadLocalRandom.current();// int randomIndex = random.nextInt(l,r);// swap(arr,l,randomIndex);int v = arr[l];// arr[l + 1..j] < v// arr[j + 1..i) >= v// i表示当前正在扫描的元素int j = l;for (int i = l + 1; i <= r; i++) {if (arr[i] < v) {swap(arr,j + 1,i);j ++;}}// 将基准值和最后一个 < v的元素交换,基准值就落在了最终位置swap(arr,l,j);return j;}}public static void main(String[] args) {int[] arr2={9,1,5,8,3,7,4,6,2};quickSort(arr2);System.out.println(Arrays.toString(arr2));}}

快速排序非递归写法

/*** 借助栈来实现非递归分治快排* @param arr*/public static void quickSortNonRecursion(int[] arr) {Deque<Integer> stack = new ArrayDeque<>();// 栈中保存当前集合的开始位置和终止位置int l = 0;int r = arr.length - 1;stack.push(r);stack.push(l);while (!stack.isEmpty()) {// 栈不为空时,说明子区间还没有处理完毕int left = stack.pop();int right = stack.pop();if (left >= right) {// 区间只有一个元素continue;}int p = partition(arr,left,right);// 依次将右区间的开始和结束位置入栈stack.push(right);stack.push(p + 1);// 再将左侧区间的开始和结束位置入栈stack.push(p - 1);stack.push(left);}

关于基准值的选取

1.选择边上(左或者右)(接近有序的数组上,快排退化成O(N^2)时间复杂度,左右区间严重不平衡)

2.随机选择

3.几数取中(例如三数取中)(array[left], array[mid], array[right] 大小是中间的为基准值)

2和3基准值的选择方法在接近有序的数组上,都能避免递归过程中,快速排序分区严重不平衡的问题

问题:当数组中重复元素较多时,就会导致分区后某个区间元素过多,会递归过程中,递归树严重不平衡,快排时间复杂度倒退到O(n^2)

为解决此问题,衍生出了两种解决方法:二路快排和三路快排

8.2 二路快速排序

二路快排在分区的时候将相等元素分到左右两个子区间

package sort;import java.util.ArrayDeque;import java.util.Arrays;import java.util.Deque;import java.util.concurrent.ThreadLocalRandom;public class SelfSevenSort {public static void swap(int[] arr,int i,int j){int tmp=arr[i];arr[i]=arr[j];arr[j]=tmp;}/*** 二路快排* @param*/public static void quickSort2(int[] arr){quickSortInternal2(arr,0,arr.length-1);}// 对 arr[l,r]进行快速排序private static void quickSortInternal2(int[] arr, int l, int r) {// 分区if (l>r) {return;}// arr[l,r]上求分区点,arr[l,p]都小于v,arr[p,r]都大于Vint p=partition2(arr,l,r);// 重复上述过程quickSortInternal2(arr,l,p-1);quickSortInternal2(arr,p+1,r);}// 二路快排的分区// 返回arr[l,r]上的分区点private static int partition2(int[] arr, int l, int r) {int v=arr[l];// arr[l + 1..i) <= v// [l + 1..l + 1) = 0int i=l+1;// arr(j..r] >= v// (r...r] = 0int j=r;while(true){// i从前向后扫描,碰到第一个 >= v的元素停止while(i<=j && arr[i]<v){i++;}// j从后向前扫描,碰到第一个 <= v的元素停止while(i<=j && arr[j]>v){j--;}if(i>=j){break;}swap(arr,i,j);i++;j--;return j;}// j落在最后一个 <= v的元素身上swap(arr,l,j);return j;}public static void main(String[] args) {int[] arr2={9,1,5,8,3,7,4,6,2};quickSort2(arr2);System.out.println(Arrays.toString(arr2));}}

8.3 三路快速排序

将所有重复的元素都放在最终位置,只需要在小于V和大于V的子区间进行快排,所有相等的元素不再处理。

arr[l] 基准值

arr[l+1,It] 区间为小于V的元素集合

It为最后橙色小于V的区间中最后一个小于V 的元素

arr[It+1,i) 蓝色区间为等于基准值的区间

arr[i] 为当前遍历到的元素

gt 为紫色区间大于V的元素集合中第一个大于V 的元素

arr[gt,r] 紫色区间 大于V的集合

当扫描的元素ar[i] < v 时,交换arr[i]和第一个等于V的元素It+1

当扫描的元素ar[i] = v 时,不用处理,继续向前走

当扫描的元素ar[i] > v 时,交换当前遍历的元素arr[i]和紫色区间第一个> v的元素,但是此时不用继续向前走,因为交换过来的元素也是未处理的元素

整个集合处理完,交换j和l

package sort;import java.util.ArrayDeque;import java.util.Arrays;import java.util.Deque;import java.util.concurrent.ThreadLocalRandom;public class SelfSevenSort {public static void swap(int[] arr,int i,int j){int tmp=arr[i];arr[i]=arr[j];arr[j]=tmp;}/*** 三路快排* @param*/public static void quickSort3(int[] arr) {quickSortInternal3(arr,0,arr.length - 1);}private static void quickSortInternal3(int[] arr, int l, int r) {if (l>r) {return;}// int randomIndex = random.nextInt(l,r);// swap(arr,l,randomIndex);int v = arr[l];// 这些变量的取值,一定是满足区间的定义,最开始的时候,所有区间都是空// arr[l + 1..lt] < v// lt是指向最后一个<v的元素int lt = l;// arr[lt + 1..i) == v// i - 1是最后一个 = v的元素int i = lt + 1;// arr[gt..r] > v// gt是第一个 > v的元素int gt = r + 1;// i从前向后扫描和gt重合时,所有元素就处理完毕while (i < gt) {if (arr[i] < v) {// arr[l + 1..lt] < v// arr[lt + 1..i) == vswap(arr,i,lt + 1);i ++;lt ++;}else if (arr[i] > v) {// 交换到gt - 1swap(arr,i,gt - 1);gt --;// 此处i不++,交换来的gt - 1还没有处理}else {// 此时arr[i] = vi ++;}}// lt落在最后一个 < v的索引处swap(arr,l,lt);// arr[l..lt - 1] < vquickSortInternal3(arr,l,lt - 1);// arr[gt..r] > vquickSortInternal3(arr,gt,r);}public static void main(String[] args) {int[] arr2={9,1,5,8,3,7,4,6,2};quickSort3(arr2);System.out.println(Arrays.toString(arr2));}}

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。