经典排序算法的总结


之前总是听一些大牛说学会了排序就相等于学会了算法,能手写个快速排序基本就能通过面试了,这一

点我是很不理解的;后来慢慢地发现这是有道理的,常见的几种排序算法中蕴含了许多经典的算法思想,

比如说递归、分治、迭代、双指针、空间换时间等,算法的问题很多,但是计算机处理问题的方式却只

有那一种。所以要归根溯源,从简单的排序算法中去学习算法思想也是一种很有效的方法

一、算法描述

对一个无序的数组进行排序 (从小到大或者从大到小)

二、算法分类标准

划分排序算法的主要标准有稳定性、内外排序、时空复杂度、是否比较排序等

1.稳定性

如果数组中有两个元素是相等的,在经过某个排序算法之后,原来前面的那个元素仍然在另一个元素的

前面,那这个排序算法就是稳定的;相反如果在原来两个相等元素中前面的一个元素被移到了后面,那就

是不稳定的

2.内外排序

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

外排序是由于数据太大,因此把数据放入磁盘中,排序需要经过磁盘和内存的数据传输才能进行

3.是否比较排序

比较排序: 一个算法在排序的过程中使用比较操作来判断两个元素的大小关系,那么这个算法

就是比较排序,常见的排序算法都是比较排序算法,比如冒泡排序、插入排序、堆排序,这些排序

算法的平均时间复杂度最快也只能是O(nlogn)

非比较排序比较典型的算法有计数排序、桶排序和基数排序,这类排序的时间复杂度可以达到

O(n)的级别

4.时空复杂度

就是时间复杂度和空间的复杂度了

三、常见的排序算法

1.冒泡排序(Bubble Sort)

(1)算法描述:冒泡排序是将相邻元素进行比较,大数慢慢地沉底,小数放置到前面

(2)算法特点

  • 内排序,所有操作在原来的数组中就可以完成了,不需要额外的空间

  • 时间复杂度是O(n^2),空间的复杂度是O(1)

  • 稳定排序: 如果两个元素的位置相同,他们的位置是不会进行交换的

(3)代码实现

#冒泡排序 Python的版本
def bubble_sort(nums):
	n = len(nums)
	for i in range(n):
		for j in range(1,n-i):
			if nums[j] < nums[j-1]:
				nums[j-1],nums[j] = nums[j],nums[j-1]
	return nums
//冒泡排序  Java版本
private void swap(int[] nums,int i,int j){
    int temp = nums[i];
    nums[i] = nums[j];
    nums[j] = temp;
}

public void bubbleSort(int[] nums){
    for(int i = nums.length - 1; i >= 1;i--){
        for(int j = 1;j <= i;j++){
            if(nums[j-1] > nums[j]){
                swap(nums,j,j-1);    //交换最大值
            }
        }
    }
}

2.选择排序(Select Sort)

(1)算法描述

选择排序的思路是先找到前n个元素中最大的值,然后和最后一个元素进行交换,以此来保证最后一个

元素一定是最大的;然后找到前n-1个元素中的最大值,和第n-1个元素进行比较,依次类推找到前两个元

素中最大的元素和第二个元素进行交换;最后就得到了排序数组

和冒泡排序的思路相似,都是要把最大的元素放到最后,但是冒泡排序在每一轮比较中交换多次

而选择排序只需要在每一轮交换依次

(2)算法特点

  • 内排序,所有操作在原来的数组中就可以完成了,不需占用额外的磁盘空间

  • 时间复杂度是O(n^2),空间的复杂度是O(1),原地算法

  • 是否稳定排序取决于使用数组还是链表存储数据,使用数组是不稳定的,但是链表是稳定的

(3)代码实现

#选择排序 Python的版本
def selectSort(nums):
    n = len(nums)
	for i in range(1,n):
        maxIndex = 0
        for j in range(0,n-i+1):
            if nums[maxIndex] < nums[j]:
                maxIndex = j
        #将最大的元素和最后一个元素进行交换
	    nums[maxIndex],nums[n-i] = nums[n-i],nums[maxIndex]
	return nums
//选择排序  Java版本
private void swap(int[] nums,int i,int j){
    int temp = nums[i];
    nums[i] = nums[j];
    nums[j] = temp;
}
public void selectionSort(int[] nums){
    for(int i = nums.length - 1;i > 0;i--){
        int maxIndex = 0;   //每轮循环开始都要记录一个最大元素的位置
        for(int j = 0;j <= i;j++){
            if(nums[maxIndex] < nums[j]){
                maxIndex = j;
            }
        }
    }
    swap(nums,maxIndex,i);   //把这个最大的元素移到最后
}

3.插入排序(InsertSort)

(1)算法描述

插入排序的思想是遍历整个数组,保持当前元素的左侧始终是排序后的数组,然后将当前元素插入到

前面排序完成的数组的对应的位置,使其保持排序的状态,类似于动态规划,先将前i - 1个元素排序完成

再去插入第i个元素,构成i个元素的有序数组

(2)算法特点

  • 内排序,所有操作在原来的数组中就可以完成了,不需要额外的空间

  • 时间复杂度:如果是已经排序的数组,复杂度就是O(n),在其他情况下都是O(n^2)

  • 稳定排序: 插入排序是相邻元素之间的交换,而不是跳跃式的交换,所以是稳定的

(3)代码实现

#冒泡排序 Python的版本
def insert_sort(nums):
	n = len(nums)
	for i in range(1,n):   #要从第二个元素开始遍历
        j = i
	    while j > 0 && nums[j] < nums[j-1]:
            nums[j-1],nums[j] = nums[j],nums[j-1]
		   j--	
	return nums
//插入排序  Java版本
private void swap(int[] nums,int i,int j){
    int temp = nums[i];
    nums[i] = nums[j];
    nums[j] = temp;
}

public void insertSort(int[] nums){
    for(int i = 1; i < nums.length;i++){  //默认第一个元素是排好序的,从第二个元素开始排
        int j = i;
        while(j > 0 && nums[j] < nums[j-1]){
            swap(nums,j,j-1);
            j--;
        }
    }
}

4.希尔排序(ShellSort)

(1)算法描述

希尔排序可以看作是冒泡排序或者插入排序的升级,这种排序算法在每次排序的时候都把数组拆分

成若干个序列,一个序列的相邻元素的索引间隔为固定的距离,每一轮对这些序列进行冒泡或者插入,

然后在缩小索引间隔得到新的序列,直到间隔为0

(2)算法特点

  • 内排序,所有操作在原来的数组中就可以完成了,不需要额外的空间

  • 时间复杂度:不同的增量序列会有不同的时间复杂度,一般介于O(n^1.5)到 O(n^2),空间复杂度是O(1)

  • 稳定性: 这个算法是不稳定的,里面有很多不相邻元素的交换操作

(3)代码实现

#希尔排序 Python的版本
def shell_sort(nums):
	n = len(nums)
    gap = n // 2
	while gap:
        for i in range(gap,n):
            while i - gap >= 0 and nums[i-gap] > nums[i]:   #插入排序
                nums[i-gap],nums[i] = nums[i],nums[i-gap]
                i -= gap
        gap >>= 1
	return nums
//希尔排序  Java版本
private void swap(int[] nums,int i,int j){
    int temp = nums[i];
    nums[i] = nums[j];
    nums[j] = temp;
}

public void shellSort(int[] nums){
    int gap = nums.length >> 1;
    while(gap > 0){
        for(int i = 0;i < gap;i++){   //对每个子序列进行排序
            for(int j = i+gap;j < nums.length;j+=gap){   //插入排序
                int temp = j;
                while(temp > i && nums[temp] < nums[temp - gap]){
                    swap(nums,temp,temp-gap);
                    temp -= gap;
                }
            }
        }
        gap >>= 1;
    }
}

5.快速排序(QuickSort)

(1)算法描述

快速排序的核心思想就是取一个元素(可以是第一个或者最后一个)作为分界点,将整个数组分成左右

两侧,左边的元素小于或者等于分界点元素,而右边的元素大于分界点的元素;再对左右子数组进行递归

即可,当子数组只有一个元素或者没有元素的时候就结束这个递归的过程

(2)算法特点

  • 内排序,所有操作在原来的数组中就可以完成了,不需要额外的空间

  • 复杂度分析:时间复杂度最佳是O(nlogn),但是如果分界点元素选择不正确会恶化到O(n^2),这种情况是

数组完全逆序时的表现。如果随机选择分界点时间复杂度能够稳定在O(nlogn)。此外,如果相同元素

数量比较多的情况下,也会降低排序的性能;空间复杂度为O(logn),属于堆栈调用,最坏情况下的

空间复杂度还是O(n),只有平均情况是O(nlogn)

  • 稳定性: 是不稳定的排序,因为包含跳跃式的交换元素

(3)代码实现

#快速排序 Python的版本 (写法一) 
def quick_sort(data):
	if len(data) >= 2:
		mid = data[len(data) - 1]  #选取一个基准值,这里选取的是最后一个元素
		left,right = [],[]   #定义基准值左右两侧的列表
		data.remove(mid)     #先从原数组中将当前元素移除
		for num in data:
			if num >= mid:
				right.append(num)   #大于基准值的放在右边
			else:
				left.append(num)    #小于基准值的放在左边
		return quick_sort(left) + [mid] + quick_sort(right)
	else:
		return data           #如果只有一个元素时直接返回
#毫无疑问这种写法很简洁,但是非常地浪费空间

#快速排序 Python的版本 (写法二) 在原数组中操作
def quick_sort(nums):
    n = len(nums)
    
    def quick(left,right):
        if left >= right:
            return nums
        pivot = left
        i = left
        j = right
        while i < j:
 			while i < j and nums[j] > nums[pivot]:
                j -= 1
             while i < j and nums[i] <= nums[pivot]:
                i += 1
             nums[i],nums[j] = nums[j],nums[i]
        nums[pivot],nums[j] = nums[j],nums[pivot]
        quick(left,j-1)
        quick(j+1,right)
        return nums
//快速排序  Java版本 (写法一)
private void swap(int[] nums,int i,int j){
    int temp = nums[i];
    nums[i] = nums[j];
    nums[j] = temp;
}

public void QuickSort(int[] nums,int left,int right){
    if(left >= right) return;
    int lo = left + 1;   //小于分界点元素最右侧的指针
    int hi = right;      //大于分界点元素最左侧的指针
    while(lo <= hi){
        if(nums[lo] > nums[left]){  //左侧指针指向的元素小于分界点元素
            swap(nums,lo,hi);
            hi--;
        }else{
            lo++;
        } 
    }
    lo--;    //回到小于分界点元素的最右侧
    swap(nums,left,lo);  //将分界点元素移到左侧数组的最右侧
    QuickSort(nums,left,lo-1);
    QuickSort(nums,lo+1,right);
}

//快速排序  Java版本 (写法二)
/*不用hi来标记分界点元素的最右侧,而是只有一个lo来标记最左侧
在遍历整个数组的过程中,如果发现了一个小于等于分界点元素的元素,就和lo+1位置的元素进行交换
然后lo自增,这样可以保证lo左侧一定都是小于等于分界点元素的,遍历到最后lo的位置就是新的分界点的位置
和最开始的分界点元素位置互换
*/

public void QuickSort(int[] nums,int left,int right){
    if(left >= right) return;
    int cur = left + 1;    //从左侧第二个元素 开始
    int lo = left;         //最左侧的元素作为基准值
    while(cur <= right){
        if(nums[cur] <= nums[left]){   //交换位置保证lo的左侧都是小于nums[left]的
            swap(nums,lo+1,cur);
            lo++;
        }
        cur++;
    }
    swap(nums,left,lo);
    QuickSort(nums,left,lo-1);
    QuickSort(nums,lo+1,right);
}

6.归并排序(QuickSort)

(1)算法描述

归并排序的核心思想就是分治法,先将数组分成子序列,再让子序列间有序,合并成有序数组

主要的步骤如下:

  • 将长度为n的输入序列分成长度为n/2的子序列

  • 对两个子序列进行归并排序

  • 合并所有的子序列

(2)算法特点

  • 内排序,所有操作在原来的数组中就可以完成了,不需要额外的空间

  • 复杂度分析:每个元素都要经过logn次的排序,所以时间复杂度是O(nlogn);排序的过程中要使用临

时数组来存放临时排序结果,空间复杂度为O(n)

  • 稳定性: 稳定排序,保证原来相同的元素能够在相同的位置

(3)代码实现

#归并排序  Python版本  实现
def MergeSort(lists):
    if len(lists) <= 1:
        num = len(lists)//2
        left = MergeSort(lists[:num])
        right = MergeSort(lists[num:])
        return Merge(left,right)
def Merge(left,right):
    r,l = 0,0
    result = []
    while l < len(left) and r < len(right):
		if left[l] < right[r]:
			result.append(left[l])
			l+=1
		else:
			result.append(right[r])
			r+=1
	result+=list(left[l:])
	result+=list(right[r:])
	return result
//归并排序  Java版本  具体实现
public void mergeSort(int[] nums,int left,int right){
    if (left >= right) return;
    int mid = (left+right) / 2;
    
    mergeSort(nums,left,mid);     //先对左右子数组进行排序
    mergeSort(nums,mid+1,right);
    
    int[] temp = new int[right-left+1];               //临时数组存放合并的结果
    int i = left,j = mid + 1;
    int cur = 0;
    while(i <= mid && j <= right){
        if(nums[i] <= nums[j]) temp[cur++] = nums[i++];
        else temp[cur++] = nums[j++]
    }
    while(i <= mid){
        temp[cur++] = nums[i++];
    }
    
    while(j <= right){
        temp[cur++] = nums[j++];
    }
    
    //合并数组完成,拷贝到原来的数组  左边界是从left开始排序的
    for(int k = 0;k < temp.length;k++){
        nums[left+k] = temp[k];
    }
}

7.堆排序(HeapSort)

(1)算法描述

首先把整个数组变成一个大顶堆,然后每次从堆顶取出最大的元素。这样依次取出的最大元素就形成

了一个排序的数组;堆排序的主要操作就是新建堆和调整堆

堆的底层数据结构数组,实际上堆可以看成一个完全二叉树,除了最后一层之外其他的每一层都被

完全填充了;对于下标为 i 的元素,它的子节点是2 * i + 1,2 * i + 2(不能超出边界),这样就可以明确

父节点和子节点之间的关系了

在新建堆的时候从左向右开始遍历,当遍历到一个元素的时候,重新排列从这个元素节点到根节点的

所有元素,保证满足最大堆的要求;遍历完成,堆也就建好了

在弹出根节点后,将树的最底层最右侧的元素转移到堆顶来,此时堆被破坏,需要重建。从根节点

开始和两个子节点比较,如果父节点比所有的子节点小,需要互换父节点和最大的子节点,然后把互换

后在子节点的位置当作新的父节点,和它的子节点比较,如此往复直到最后一层,重建完成

(2)算法特点

  • 内排序,所有操作在原来的数组中就可以完成了,不需要额外的空间

  • 复杂度分析:时间复杂度稳定在O(nlogn),因为在构建堆的时候对于每个元素需要进行O(logn)次比较

时间复杂度是O(nlogn);弹出每个元素重建堆也是需要调整O(logn)次,时间复杂度是O(nlogn)

所以整体的时间复杂度是O(nlogn) 。 空间复杂度是O(1),在原数组内进行所有操作即可

  • 稳定性: 不稳定的排序,新建堆和调整堆的过程都会打乱元素的相对位置

(3)代码实现

#堆排序 Python版本
def heap_sort(nums):
    #调整堆
    def adjust_heap(nums,startpos,endpos):
        pos = startpos              #父节点
        maxchildpos = pos * 2 + 1   #最大子节点,初始化成左子节点
        if maxchildpos < endpos:
            rightpos = maxchildpos + 1
            if rightpos < endpos and nums[rightpos] > nums[childpos]:
                maxchildpos = rightpos   #右子节点比左子节点大
            if nums[maxchildpos] > nums[pos]:
                nums[pos],nums[maxchildpos] = nums[maxchildpos],nums[pos]
            	adjust_heap(nums,pos,endpos)
 
    n = len(nums)
    #建堆
    for i in reversed(range(n//2)):
        adjust_heap(nums,i,n)
    #调整堆
    for i in range(n-1,-1,-1):
        nums[0],nums[i] = nums[i],nums[0]
        adjust_heap(nums,0,i)
    return nums
//堆排序  Java版本  
public void heapSort(int[] nums){
    heapify(nums);        //新建一个大顶堆
    
    for(int i = nums.length - 1;i >= 1;i--){
        swap(nums,0,i);   //弹出最大的堆顶放在最后  因为要从小到大地进行排序
        rebuildHeap(nums,0,i-1);  //重新调整最大堆
    }
}

//将一个数组堆化
public void heapify(int[] nums){
    for(int i = 1;i < nums.length;i++){
        int par = (i-1)>>1;    //找到父节点
        int child = i;         //定义子节点
        while(child > 0 && nums[par] < nums[child]){  //从子节点到父节点构建最大堆
            swap(nums,par,child);
            child = par;
            par = (par-1) >> 1;    //把父节点当成子节点去寻找新的父节点
        }
    }
}

//重建堆
public void rebuildHeap(int[] nums,int par,int last){
    int left = 2*par + 1;   //左子节点
    int right = 2*par + 2;  //右子节点
    int maxIndex = left;
    
    if(right <= last && nums[right] > nums[left]){
        maxIndex = right;             //maxIndex是最大子节点下标的索引
    }
    
    if(left <= last && nums[par] < nums[maxIndex]){ //和最大的子节点进行比较
        swap(nums,par,maxIndex);                    //互换到最大子节点
        rebuildHeap(nums,maxIndex,last);            //重建最大子节点代表的子树
    }
}

8.计数排序(CountingSort)

(1)算法描述

计数排序是典型的空间换时间的算法,开辟额外数据空间存储元素索引和元素值出现的个数

具体的过程如下:

  • 创建一个长度为数组中最小和最大元素之差的数组,分别对应数组中的每个元素

  • 使用新创建的数组来统计每个元素出现的频率,然后遍历新的数组,根据每个元素出现的频率

将元素放回到老的数组中,得到已经排序好的数组

(2)算法特点

  • 外排序,对于数据范围很大的数组,需要大量的时间和内存

  • 复杂度分析:时间复杂度为O(n + r) 其中r为数组元素变化的范围,随着数组范围的变大,计数排序

的性能逐渐地在降低;空间复杂度为O(n + r),随着数组元素范围的增大,空间复杂度也会

增大

  • 稳定性: 计数排序是稳定的,原先排在前面的元素仍然是排在前面

(3)代码实现

#计数排序  Python版本
def counting_sort(nums):
    if not nums: return []
    n = len(nums)
    minValue = min(nums)
    maxValue = max(nums)
    tempArr = [0] * (maxValue - minValue + 1)
    
    for num in nums:
        tempArr[num - minValue] += 1
    j = 0
    for i in range(n):
        while tempArr[j] == 0:
            j += 1
        nums[i] = j + minValue
        tempArr[j] -= 1
     return nums
//计数排序 Java版本
public void countSort(int[] nums){
    int min = Integer.MAX_VALUE;
    int max = Integer.MIN_VALUE;
    for(int num : nums){           //找到最大值和最小值
        min = Math.min(min,num);
        max = Math.max(max,num);
    }
    
    int[] count = new int[max-min+1];  //建立新数组
    for(int num : nums){               //统计每个元素出现的次数
        count[num-min]++;
    }
    int cur = 0;              //用来表示原数组的指针
    for(int i = 0;i < count.length;i++){   //根据出现的频率将数组中的元素放回到旧数组中
        while(count[i] > 0){
            nums[cur++] = i + min;
            count[i]--;s
        }   
    }
}

9.基数排序(CountingSort)

(1)算法描述

基数排序的思想就是对数字中的每一位进行排序,从最低位开始

具体的过程如下:

  • 找到数组中的最大值,先得到最大的位数

  • 从最低位开始去每个位组成radix数组

  • 对radix进行计数排序(计数排序比较适合小范围内的排序)

(2)算法特点

  • 外排序,因为需要使用辅助的数组来临时存储元素

  • 复杂度分析:时间复杂度为O(k*n),其中k为绝对值最大元素的位数;空间复杂度也是线性的

  • 稳定性: 基数排序是稳定的,在排序添加元素的过程中没有改变相同元素的位置关系

(3)代码实现

#基数排序  Python版本
def Radix_sort(nums):
    if not nums: return []
    maxValue = max(nums)   #找到最大值
    maxDigit = len(str(maxValue))  #最大的位数
    bucketList = [[] for _ in range(10)]   #存放每一位相同的元素列表 二维列表
    
    #要从最低位开始排序
    div,mod = 1,10
    for i in range(maxDigit):
        for num in nums:
            bucketList[num % mod // div].append(num)
        div *= 10        
        mod *= 10
        
        idx = 0       #原来数组下标的索引
        #每一轮排序完将桶内的元素放回原数组中
        for j in range(10):                 #每位数字只能是0-9,总共有10个
            for item in bucketList[j]:      
                nums[idx] = item
                idx += 1
            bucketList[j] = []              #将桶内的元素取出后,清空该桶
    return nums
//基数排序 Java版本
//考虑到数组中还有负数的情况 可以把桶的大小扩大到19个  -9 - 9

public Radix_Sort(int[] nums){
    int max = Integer.MIN_VALUE;
    int min = Integer.MAX_VALUE;
    for(int num : nums){   //计算最大值和最小值
         max = Math.max(max,num);
         min = Math.min(min,num);
    }
    
    max = Math.max(max,-min);    //求得绝对值的最大值
    int digits = 0;
    while(max > 0){              //计算最大值的位数               
        max /= 10;
        digits++;
    }
    
    List<Integer>[] buckets = new List[19];    //创建一个包含一个所有位数的数组
    for(int i = 0;i < buckets.length;i++){
        buckets[i] = new ArrayList<>();       //实际上相当于是二维列表
    }
    
    int pos;
    int cur;
    for(int i = 0,mod = 1;i < digits;i++,mod *= 10){   //对10进制的每一位进行基数排序
        for(int num : nums){              //扫描数组将值放入对应的桶中
            pos = (num / mod) % 10;       // -9对应的索引为0
            buckets[pos+9].add(num);
        }
        cur = 0;   //指向原数组的索引
        for(List<Integer> bucket : buckets){   //将桶内的元素放回到原来的数组中
            if(bucket != null){
                for(Integer item : bucket){
                    nums[cur++] = item;
                }
                bucket.clear();                //将桶清空,已备下次使用
            }   
        } 
    }
}

10.桶排序(BucketSort)

(1)算法描述

桶排序就是将所有的元素分布到一系列的桶中,然后对每个桶里面的所有元素分别进行排序

具体的过程如下:

  • 新建一个桶的数组,提前制定好桶的规则,比如元素0-9在一个桶,10-19为一个桶

  • 遍历待排序的数组,将元素分别分配到对应的桶中

  • 对每个桶中的元素进行排序

  • 最后将所有的桶中的元素还原到原数组中得到最后的排序数组

(2)算法特点

  • 外排序,因为需要使用辅助的数组来临时存储元素

  • 复杂度分析:时间复杂度是O(n+r),随着元素范围的扩大,时间的消耗也在不断增大,其中r为数组元素变

化的范围;空间复杂度也是O(n+r),需要额外的空间来保存所有的桶和桶中的元素

  • 稳定性: 桶排序是稳定的,与计数排序是类似的

(3)代码实现

  def bucketSort(nums,bucketSize):
      if len(nums) < 2:   #递归终止的条件
          return nums
      minValue = min(nums)
      maxValue = max(nums)
      
      #需要的桶个数
      bucketNum = (maxValue - minValue)//bucketSize + 1;   #向下取整的 要加1
      buckets = [[] for _ in range(bucketNum)]
      
      for num in nums:
          #放入对应的桶中
          buckets[(num - minValue) // bucketSize].append(num)
      res = []
      for bucket in buckets:
          if not bucket: continue
          if bucketSize == 1:
              res.extend(bucket)
          else:
              #都装在一个桶里
              if bucketNum == 1:
                  bucketSize -= 1
              res.extend(bucketSort(bucket,bucketSize))
      return res
public void bucketSort(int[] nums){
    int INTERVAL = 100;     //定义桶的大小
    int min = Integer.MAX_VALUE;
    int max = Integer.MIN_VALUE;
    for(int num : nums){   //找出最大值和最小值
        min = Math.min(min,num);
        max = Math.max(max,num);
    }
    
    int count = (max - min + 1);    //数据范围
    int bucketSize = (count % INTERVAL == 0)? (count / INTERVAL) : (count / INTERVAL + 1);
    
    List<Integer>[] buckets = new List[bucketSize];
    
    for(int num : nums){   //将所有的元素都放入对应的桶里面
        int pos = (num - min)/INTERVAL;
        if(buckets[pos] == null) buckets[pos] = new ArrayList<>();
        buckets[pos].add(num);
    }
    
    int cur = 0;
    for(List<Integer> bucket:buckets){
        if(bucket != null){
            bucket.sort(null);     //对每个桶进行排序
            for(Integer item : bucket){
                nums[cur++] = item;
            }
        }
    }
}

11.二叉搜索树排序(Binary-TreeSort)

(1)算法描述

二叉搜索树排序使用数组中内的所有元素构建一颗二叉搜索树,然后用中序遍历将所有的元素填充

回原来的数组中。搜索二叉树不能用数组表示,所以必须使用额外的数据结构来构建二叉树

(2)算法特点

  • 外排序,因为需要使用辅助的数组来临时存储元素

  • 复杂度分析:最差情况下整个数组是排好序的,时间复杂度是O(n^2),二叉树会变成一个链表结构;但是

在最优和平均情况下时间复杂度在O(nlogn)的水平;空间复杂度为O(n),因为要构建一个包含n个

元素的二叉搜索树

  • 稳定性: 这个排序是稳定的,构建二叉树的过程中能够保证元素的一致性

(3)代码实现

class TreeNode{
    int val;        //树节点的数据结构
    TreeNode left;
    TreeNode right;
    
    public TreeNode(int val){
        this.val = val;
    }
}

public int[] BSTSort(int[] nums){
    TreeNode root = new TreeNode(nums[0]);    //构建一个根节点
    for(int i = 1;i < nums.length;i++){       //将所有的元素插入到二叉搜索树中
        buildTree(root,nums[i]);
    }
    midTraverse(root,nums,new int[1]);      //中序遍历获取二叉树中的所有节点
    return nums;
}

//中序遍历二叉树
public void midTraverse(TreeNode node,int[] nums,int[] pos){
    if(node == null) return;
    midTraverse(node.left,nums,pos);
    nums[pos[0]++] = node.val;
    midTraverse(node.right,nums,pos);
}
//构建一颗二叉搜索树
public void buildTree(TreeNode node,int num){
    if(node == null) return;
    if(num >= node.val){    //大于要插入到右子树
        if(node.right == null){
            node.right == new TreeNode(num);
        }else{
            buildTree(node.right,num);
        }
    }else{    //小于的话插入到左子树
        if(node.left == null){
            node.left = new TreeNode(num);
        }else{
            buildTree(node.left,num);
        }
    }
}