常用的排序八大算法:
时间复杂度、空间复杂度及稳定性介绍:

算法的稳定性
稳定性定义:排序前后两个相等的数相对位置不变,则算法稳定。
稳定性得好处:从一个键上排序,然后再从另一个键上排序,第一个键排序的结果可以为第二个键排序所用
1、冒泡排序
原理:
(1)冒泡排序指的是从数组开头的元素开始,与下一个元素依次进行比较,按照所要求的排序规则,在满足要求之后进行交换
(2)对于长度为n的数组,最多循环 n 次进行比较
(3)在进行比较的时候,可能在进行 m 次循环之后就已经满足要求了,这时应该使用flag进行标记,满足要求则不再进行比较
代码如下:
public static void bubbleSort(int[] arr){
int temp = 0;
boolean flag = false;
for(int i = 0;i < arr.length;i++){
for(int j = 0;j < arr.length-1;j++){
if(arr[j] > arr[j+1]){//这时升序的情况,如果需要降序排列,则改为小于号
flag = true;
temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
if(flag){
flag = false;
}else break;
}
}
2、选择排序
原理:
(1)选择排序指的是对于给定的数组,每次循环需要选择出最小值,与当前下标所在位置的值进行交换
(2)使用 minIndex 进行最小下标的标记,如果当前下标对应的值就是最小值(minIndex == i),则不用进行交换
代码如下:
public static void selectSort(int[] arr){
for(int i = 0;i < arr.length-1;i++){
int minIndex = i;
int min = arr[i];
for(int j = i+1;j < arr.length;j++){
if(min > arr[j]){//此处为升序排序,如果需要降序,则改为小于号
minIndex = j;
min = arr[j];
}
}
if(minIndex != i){
arr[minIndex] = arr[i];
arr[i] = min;
}
}
}
3、插入排序
原理:
(1)插入排序指的是对于给定的数组,需要给当前下标对应的元素找到一个合适的位置,即插入
(2)对于每一个下标,都有对应的插入下标(insertIndex )与插入的值(insertValue ),将 insertValue 与之前的值进行比较,直到找到正确的位置,进行插入
(3)如果插入的位置就是当前的位置(insertIndex+1 == i),则不用插入
代码如下:
public static void insertSort(int[] arr){
int insertIndex = 0;
int insertValue = 0;
for(int i = 1;i < arr.length;i++){
insertValue = arr[i];
insertIndex = i-1;
while(insertIndex >= 0 && insertValue < arr[insertIndex]){//此处为升序,如果要降序排列,则改为小于号
arr[insertIndex+1] = arr[insertIndex];
insertIndex--;
}
if(insertIndex+1 != i){
arr[insertIndex+1] = insertValue;
}
}
}
4、快速排序
原理:
(1)快速排序的名字得益于其排序的速度。
(2)每次进行排序的时候,对于给定的左右边界(left、right),找到中间值(pivot),按照排序的规则,找到不满足要求的下标,并将左右下标对应的元素值进行交换。
例如:
对于数组{1,5,-4,3,6,8,4,8,5,4}来说,length = 10,中间位置对应的元素为 8 。
如果要求升序排列的话,就需要让 8 左边的元素都小于 8 ,右边的元素都大于 8 。
(3)在进行交换的时候会出现特殊的情况,比如:
①原来的数组就是有序的情况下,会造成左右部分的下标相等,即 l >= r,这个时候应该结束循环
②如果交换完了之后发现arr[l] = pivot,则 r–
③ 如果交换完了之后发现arr[r] = pivot,则 l++
(4)如果交换完之后,l==r,必须 l++ , r–,否则死循环,栈溢出
(5)在循环结束之后,左右两边都满足要求的情况下,就进行递归操作,将左半部分的元素再次分割为两个部分,进行排序,右半部分同理。
具体代码如下:
public static void quickSort(int[] arr,int left,int right){
int l = left;
int r = right;
int pivot = arr[(left+right)/2];
int temp = 0;
//把比pivot小的值放在左边,把比pivot大的值放在右边
while(l < r){
//从左边找,一直找到大于等于pivot值
while(arr[l] < pivot){
l+=1;
}
//从右边找,一直找到小于等于pivot值
while(arr[r] > pivot){
r-=1;
}
//如果l>=r,说明左右两边已经满足要求:左边全部小于等于pivot,右边全部大于等于pivot
if(l >= r) break;
//交换
temp = arr[l];
arr[l] = arr[r];
arr[r] = temp;
//如果交换完了之后发现arr[l] == pivot,则r--
if(arr[l] == pivot){
r-=1;
}
//如果交换完了之后发现arr[r] == pivot,则l++
if(arr[r] == pivot){
l+=1;
}
}
// System.out.println(Arrays.toString(arr));
//如果交换完之后,l==r,必须l++,r--,否则死循环,栈溢出
if(l==r){
l+=1;
r-=1;
}
//如果最后left是小于r的,那么继续递归进行left-r的排序
if(left < r){
quickSort(arr,left,r);
}
//如果最后right是大于l的,那么继续递归进行l-right的排序
if(right > l){
quickSort(arr,l,right);
}
}
5、归并排序
原理:
(1)运用的是分而治之的思想,将给定的数组分为两个部分,对这两个部分的元素进行排序。而这两个部分又继续分别分为两个数组,如此反复,最后不能再继续分割的时候进行合并
代码如下:
//分
public static void mergeSort(int[] arr, int left, int right, int[] temp) {
if (left < right) {
int mid = (left + right) / 2;
mergeSort(arr, left, mid, temp);
mergeSort(arr, mid + 1, right, temp);
merge(arr, left, mid, right, temp);
}
}
//和
private static void merge(int[] arr, int left, int mid, int right, int[] temp) {
int i = left, j = mid + 1, t = 0;
while (i <= mid && j <= right) {
if (arr[i] < arr[j]) {
temp[t] = arr[i];
i++;
t++;
} else {
temp[t] = arr[j];
j++;
t++;
}
}
while (i <= mid) {
temp[t] = arr[i];
i++;
t++;
}
while (j <= right) {
temp[t] = arr[j];
j++;
t++;
}
t = 0;
while(left <= right){
arr[left] = temp[t];
left++;
t++;
}
}
6、希尔排序
原理:
(1)该方法实质上是一种分组插入方法
(2)把数组按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至 1 时,整个文件恰被分成一组,算法便终止。
代码如下:
public static void shellSort(int[] arr){
int temp = 0;
for(int gap = arr.length/2;gap >0;gap/=2){
for(int i = gap;i < arr.length;i++){
for(int j = i - gap;j >= 0;j-=gap){
if(arr[j] > arr[j+gap]){//此处为升序排序,降序排序改为小于号即可
temp = arr[j];
arr[j] = arr[j+gap];
arr[j+gap] = temp;
}
}
}
}
}
7、堆排序
原理:
(1)对于一个数组来说,如果要进行堆排序,就是将整个数组看做是一个完全二叉树,比如说数组{50,45,40,20,25,35,30,10,15},变成完全二叉树之后如下:
(2)堆排序中各个元素满足一定的规律:
大顶堆:arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2]
小顶堆:arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2]
(3)基本思想:将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了。
代码如下:
public static void heapSort(int[] arr){
int len = arr.length;
for(int i = len/2-1;i >= 0;i--){
adjustHeap(arr,i,len);
}
for(int i = len-1;i >= 0;i--){
int temp = arr[i];
arr[i] = arr[0];
arr[0] = temp;
adjustHeap(arr,0,i);
}
}
public static void adjustHeap(int[] arr,int parent,int length){
int temp =arr[parent];
int lChild = 2*parent + 1;
while(lChild < length){
int rChild = lChild + 1;
while(rChild < length && arr[lChild] < arr[rChild]){
lChild++;
}
if(temp >= arr[lChild]) break;
arr[parent] = arr[lChild];
parent = lChild;
lChild = lChild*2+1;
}
arr[parent] = temp;
}
再简单总结下堆排序的基本思路:
a.将无需序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆;
b.将堆顶元素与末尾元素交换,将堆顶元素"沉"到数组末端;
c.重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序。
8、基数排序
原理:
(1)基础排序又称为“桶排序”,核心思想是将所有待比较数值统一为同样的长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序,这样从最低位排序一直到最高位排序完成之后,数列就变成了一个有序序列。
(2)首先遍历数组得到最大的元素,以这个最大元素的位数(maxLen)构件一个二位桶数组 bucket ,10行maxLen列
(3)构件一个长度为 10 的一维数组 bucketElementCounts ,用来统计每一个元素出现的次数
(4)遍历数组,找到每一个元素的个位数元素,以元素为行、元素出现的次数为列,放到对应的桶数组中,此时数组 bucketElementCounts 对应位置上的值+1
(5)进行数组的还原,对数组 bucketElementCounts 进行遍历,如果元素不为0,说明有数位分布在该位置,则通过之前的桶数组,将值取出存储到原数组中,最后将数组 bucketElementCounts 对应位置元素置 0
(6)如此反复,直到排序到最高位,即可完成整个数组的排序
代码如下:
public static void radixSort(int[] arr){
int max = arr[0];
for(int i = 1;i < arr.length;i++){
if(arr[i] > max) max = arr[i];
}
int maxLen = (max + "").length();
int[][] bucket = new int[10][arr.length];
int[] bucketElementCounts = new int[10];
for(int i = 0,n = 1;i < maxLen;i++,n*=10){
for(int j = 0;j < arr.length;j++){
int digitOfElement = arr[j] / n % 10;
bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
bucketElementCounts[digitOfElement]++;
}
int index= 0;
for(int k = 0;k < bucketElementCounts.length;k++){
if(bucketElementCounts[k] != 0){
for(int l = 0;l < bucketElementCounts[k];l++){
arr[index++] = bucket[k][l];
}
}
bucketElementCounts[k] = 0;
}
}
}
总结
整个介绍比较详细,但是缺少图的演示,建议自己构造一个数组,一步步的进行推导演示,这样可以帮助理解并且记忆更加深刻。
如果大家看完之后还有什么问题,欢迎评论交流!
如果大家觉得这篇文章写的不错的话,给做着一个赞吧~