排序算法小结
一、堆排序
步骤:
- 初始化堆(从最后一个非叶子节点为根节点的子树开始,nums.length / 2 - 1);
- 调整大顶堆,将最大的数放到末尾,之后进行新一轮调整。
class Solution {
public int heapSort(int[] nums, int k) {
int len = nums.length;
buildMaxHeap(nums, len);
for (int len - 1; i > 0; i--) {
// 将最大值交换到数组最后
swap(arr, 0, i);
// 调整剩余数组,使其满足大顶堆
maxHeapify(arr, 0, i);
}
}
public void buildMaxHeap(int[] nums, int heapSize){
int i = heapSize / 2 - 1;//从最后一个非叶子节点开始建立堆
for(int j = i; j >= 0; j--){
maxHeapify(nums, j, heapSize);
}
}
public void maxHeapify(int[] nums, int i, int heapSize){//i是当前堆的根节点
int leftChild = 2 * i + 1;
int rightChild = leftChild + 1;
int largest = i;
if(leftChild < heapSize && nums[leftChild] > nums[largest]){
largest = leftChild;
}
if(rightChild < heapSize && nums[rightChild] > nums[largest]){
largest = rightChild;
}
if(largest != i){
swap(nums, largest, i);
maxHeapify(nums, largest, heapSize);
}
}
public void swap(int[] nums, int i, int j){
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
二、快速排序
每轮一个pivot,左边的数比其小,右边的比其大。
class Solution {
private static final Random RANDOM = new Random();
public int[] sortArray(int[] nums) {
quickSort(nums, 0, nums.length - 1);
return nums;
}
//代码简洁版的快排
public void quickSort(int[] nums, int begin, int end){
if(begin >= end){
return;
}
int random = begin + RANDOM.nextInt(end - begin + 1);//pivot预处理,防止递归树偏斜
swap(nums, random, begin);
int left = begin;//也是pivot
int right = end;
while(left < right){
while(left < right && nums[right] >= nums[begin]){
right--;
}
while(left < right && nums[left] <= nums[begin]){
left++;
}
swap(nums, left, right);
}
swap(nums, begin, left);
quickSort(nums, begin, left - 1);
quickSort(nums, left + 1, end);
}
public void swap(int[] nums, int i, int j){
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
三、归并排序
自顶向下不断拆分数组,直到不可分,再自底向上不断排序子数组并合并。
class Solution {
public int sort(int[] nums) {
int[] res = new int[nums.length];
mergeSort(nums, 0, nums.length - 1, res);
}
public void mergeSort(int[] nums, int head, int tail, int[] res){
if(head == tail) return;
int mid = head + (tail - head) / 2;
mergeSort(nums, head, mid, res);
mergeSort(nums, mid + 1, tail, res);
merge(nums, head, tail, res);
}
public void merge(int[] nums, int head, int tail, int[] res){
int mid = head + (tail - head) / 2;
int index1 = head;
int index2 = mid + 1;
int index = head;
while(index1 <= mid && index2 <= tail){
if(nums[index1] <= nums[index2]){
res[index++] = nums[index1++];
}else{
res[index++] = nums[index2++];
}
}
while(index1 <= mid){
res[index++] = nums[index1++];
}
while(index2 <= tail){
res[index++] = nums[index2++];
}
while(head <= tail){
nums[head] = res[head++];
}
}
}
四、计数排序
步骤:
- 求得数组最大值与最小值差值,得到计数数组counting的大小。
- counting数组记录每个数的个数。
- counting数组记录每个数应在的位置。
- 通过counting数组记录的位置,将原数组的元素一一赋值到临时数组中。
- 复制回原数组
class Solution {
public int[] sortArray(int[] nums) {
countingSort(nums);
return nums;
}
public void countingSort(int[] nums){
int max = nums[0];
int min = nums[0];
for(int i = 0; i < nums.length; i++){
if(nums[i] > max){
max = nums[i];
}else if(nums[i] < min){
min = nums[i];
}
}
int range = max - min + 1;
int[] counting = new int[range];
int[] res = new int[nums.length];
for(int element : nums){
counting[element - min]++;
}
counting[0]--;
for(int i = 1; i < range; i++){
counting[i] = counting[i] + counting[i - 1];//该值最后一个元素的位置
}
for(int i = nums.length - 1; i >= 0; i--){
res[counting[nums[i] - min]--] = nums[i];
}
for(int i = 0; i < nums.length; i++){
nums[i] = res[i];
}
}
}
五、基数排序
- 非负整数,counting数组长度为10;有负数,长度为19。
- 先求最大绝对值数字的长度,并得到其位数。
- 从最低位开始循环,每一轮步骤类似计数排序2,3,4步,并且维护一个变量dev,每轮自乘10,保证每轮计算的是下一个高位的数字。
class Solution {
public int sort(int[] nums) {
int len = nums.length;
radixSort(nums);
}
public void radixSort(int[] nums){
int len = nums.length;
int max = 0;
for(int element : nums){
if(element > max){
max = element;
}
}
int maxLength = 0;
while(max != 0){
max /= 10;
maxLength++;
}
int dev = 1;
int[] res = new int[len];
for(int i = 0; i < maxLength; i++){
int[] counting = new int[10];
for(int element : nums){
int radix = element / dev % 10;
counting[radix]++;
}
counting[0]--;
for(int j = 1; j < 10; j++){
counting[j] += counting[j - 1];
}
for(int j = len - 1; j >= 0; j--){
int radix = nums[j] / dev % 10;
res[counting[radix]--] = nums[j];
}
System.arraycopy(res, 0, nums, 0, len);
dev *= 10;
}
}
}
六、Arrays.sort()逻辑
对于基本类型的数组排序时,len < 47,双插入排序(每次从左边选两个数,先插入大的,然后小的从大的插入的位置往前寻找插入位置); 47 < len < 286,双轴快排(从中间位置往前后各走两次1/7数组长度,选取5个备选轴,一般情况选择2,4轴,如果中间区域过大,超过了数组长度的4/7,则剔除中间区域的左右边界进行下一轮排序,也就是[pivot1, pivot2]变为了(pivot1, pivot2),如果备选轴有元素相等,则改为单轴快排,选取第3个备选轴);len > 286,优先使用类似TimSort的排序方法(归并排序的改进,检测单调递增和递减的子数组,每一块都记为一个run数组,遇到递减的子数组就反转),但是若连续相同的元素达到了33个,或者子数组的个数达到了67个,就改为双轴快排。
对于非基本类型的数组排序采用TimSort或者归并排序,JDK1.7之前是默认归并,1.7及以后默认TimSort,但可以手动更改默认,继续使用归并。