学习来源: 博客、 leetcode912、 数据结构学习可视化
1、时间复杂度
2、快速排序
- 快速排序的思想:通过一趟排序将数组分割成两个独立的部分,其中一部分记录的值都比另一部分的值小,然后再将这两个部分继续进行排序,从而达到整个数组有序。
class Solution {
public int[] sortArray(int[] nums){
quickSort(nums, 0, nums.length - 1);
return nums;
}
public void quickSort(int[] nums, int low, int high){
if(low < high){
//找到一个基准,以这个在这个基准的左边都是比它小的
//在这个基准右边都是比它大的
int mid = partition(nums, low, high);
//两个部分分别再进行排序,从而使得整个数组有序
quickSort(nums, low, mid - 1);
quickSort(nums, mid + 1, high);
}
}
public int partition(int[] nums, int low, int high){
int pivot = nums[low];
while(low < high){
//high指向的值都比基准值大
while(low < high && nums[high] >= pivot){
high--;
}
//找到一个比基准还小的,进行交换
if(low < high){
nums[low] = nums[high];
}
//low指向的值都比基准小
while(low < high && nums[low] <= pivot){
low++;
}
//找到一个比基准还大的,进行交换
if(low < high){
nums[high] = nums[low];
}
}
//最后基准值停在low指向的位置
nums[low] = pivot;
//此时low已经进行了初次的分割
return low;
}
}
3、归并排序
- 归并排序的基本思想:采用分治的思想。是将已经有序的子序列进行合并,得到完全有序的序列。
class Solution {
public int[] sortArray(int[] nums){
mergeSort(nums, 0, nums.length - 1);
return nums;
}
public void mergeSort(int[] nums, int low, int high){
if(low < high){
//找到归并的中心位置,以该位置进行划分。
int mid = low + ((high - low) >> 1);
//不断的缩小问题的规模
mergeSort(nums, low, mid);
mergeSort(nums, mid + 1, high);
//将缩小后的问题一步一步的进行合并,使之有序
merge(nums, low, mid, high);
}
}
public void merge(int[] nums, int low, int mid, int high){
//临时数组的大小为high - low + 1,
//不断分割之后,最开始是两个数进行比较合并的
//以5,3为例,此时合并的结果就是3,5,临时数组的大小自然是2,而不是1.
int[] temp = new int[high - low + 1];
//i永远指向值小的,而j永远大的序列的第一个位置,即:mid + 1
int i = low, j = mid + 1;
//进行合并计数的
int idx = 0;
//小序列的范围是[low, mid],大序列的范围是[mid + 1, high]
while(i <= mid && j <= high){
//值的比较
if(nums[i] <= nums[j]){
temp[idx++] = nums[i++];
}else {
temp[idx++] = nums[j++];
}
}
//没有比较完全的
while(i <= mid){
temp[idx++] = nums[i++];
}
while(j <= high){
temp[idx++] = nums[j++];
}
//返回到nums数组
for(int count = 0; count < idx; count++){
nums[low + count] = temp[count];
}
}
}
4、堆排序
- 堆的性质:子结点的值总是小于或者大于父结点。
- 堆排序的基本思想:
- 将初始数组构建成大顶堆,此时堆是无序的。
- 将堆顶元素和最后一个元素进行交换,此时无序区间就是(R1,…,Rn-1),有序区间是(Rn)。
- 交换过后堆顶可能不满足堆的性质,那么要对无序区间调整为新堆,然后再将R1与最后一个元素进行交换。
- 不断重复上述过程直到有序区间的元素个数为n-1,排序过程完成。
class Solution {
public int[] sortArray(int[] nums){
//先进行大顶堆的构建
buildMaxHeap(nums);
for(int i = nums.length - 1; i > 0; i--){
swap(nums, 0, i);//将堆顶元素和最后一个叶子结点交换,最大值放在数组末尾。
heapify(nums, 0, i);//对前i个元素构建新的大顶堆
}
return nums;
}
//从第一个非叶子节点,从右向左,从下到上
public void buildMaxHeap(int[] nums){
for(int i = nums.length / 2 - 1; i >= 0; i--){
heapify(nums, i, nums.length);
}
}
//以i为根的堆进行调整
public void heapify(int[] nums, int i, int length){
//由数组的下标特性决定的,i为根的话,左右子结点就是下方形式
int left = 2 * i + 1;
int right = 2 * i + 2;
int largest = i;//认为根节点i是最大值
//左子结点存在并且大于根节点的值
if(left < length && nums[left] > num[largest]){
largest = left;
}
//右子结点存在并且大于根的值
if(right < length && nums[right] > nums[largest]){
largest = right;
}
//现在最大值已经不是当时认为的i
if(largest != i){
//进行交换
swap(nums, i, largest);
//在重构它的子结点的,也调整为大顶堆的形式
heapify(nums, largest, length);
}
}
public void swap(int[] nums, int i, int j){
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
5、总结
- 只有归并排序是稳定的,只有堆排序的空间复杂度为O(1),快排为O(nlogn),归并排序为O(n),时间复杂度上,快排最差为 O ( n 2 ) O(n^2) O(n2),其他的都是O(nlogn).
- 快排:首先是去找一个基准pivot,随后对这个pivot的两端进行排序。找pivot的时候,默认是nums[low],low指针找比pivot大的,high指针找比pivot小的,一旦找到,进行交换。最后pivot放在low位置。
- 归并排序:采用了分治的思想,其实就是在不断的进行二分。所以在最小的粒度,是一个一个单独的元素。此时就需要将元素进行合并。合并的时候,需要每次合并较小段的开始、结尾以及较大段的开始、结尾(这里,mid就充当了较小段的结尾,以及mid+1就是较大段的开头)。然后就是不断的进行比较合并,注意,此时需要使用一个临时的数组,最后才转移到给定的数组中。
- 堆排序:先进行大顶堆的构建,然后每次将最顶端的节点和当前节点进行交换,交换之后还要进行对的重构。在大顶堆构建的时候,是nums.length / 2 - 1进行逆向构建的,每次进行调整堆的调整。对调整是当前节点和长度,记录最节点
(2*i+1)
和右节点(2*i+2)
,假定当前结点是最大的,如果左右节点都比最大节点值大,那么就交换,一旦最大的节点不是当前结点,进行交换,然后重构。