1.煞笔快排
假设我们对数组{7, 1, 3, 5, 13, 9, 3, 6, 11}进行快速排序。
首先在这个序列中找一个数作为基准数,为了方便可以取第一个数。
遍历数组,将小于基准数的放置于基准数左边,大于基准数的放置于基准数右边。
此时得到类似于这种排序的数组{3, 1, 3, 5, 6, 7, 9, 13, 11}。
在初始状态下7是第一个位置,现在需要把7挪到中间的某个位置k,也即k位置是两边数的分界点。
那如何做到把小于和大于基准数7的值分别放置于两边呢,我们采用双指针法,从数组的两端分别进行比对。
先从最右位置往左开始找直到找到一个小于基准数的值,记录下该值的位置(记作 i)。
再从最左位置往右找直到找到一个大于基准数的值,记录下该值的位置(记作 j)。
如果位置i<j,则交换i和j两个位置上的值,然后继续从(j-1)的位置往前和(i+1)的位置往后重复上面比对基准数然后交换的步骤。
如果执行到i==j,表示本次比对已经结束,将最后i的位置的值与基准数做交换,此时基准数就找到了临界点的位置k,位置k两边的数组都比当前位置k上的基准值或都更小或都更大。
上一次的基准值7已经把数组分为了两半,基准值7算是已归位(找到排序后的位置)。
通过相同的排序思想,分别对7两边的数组进行快速排序,左边对[left, k-1]子数组排序,右边则是[k+1, right]子数组排序。
利用递归算法,对分治后的子数组进行排序。
static void sort(int[] nums, int start, int end) {
if (start >= end) return;
int tmp = nums[start];
int i = start, j = end;
while (i < j) {
// while是i < j的话,i要从start开始 而不是start+1
while (i < j && nums[j] >= tmp) --j;
while (i < j && nums[i] <= tmp) ++i;
swap(nums, i, j);
}
swap(nums, start, i);
for (int num : nums) System.out.print(num + ", ");
System.out.println();
sort(nums, start, i - 1);
sort(nums, i + 1, end);
}
static void swap(int[] nums, int i, int j) {
int tmp = nums[i];
nums[i] = nums[j];
nums[j] = tmp;
}
归并
static void mergeSort(int[] nums, int start, int end) {
if (start >= end) return;
int mid = ((end - start) >> 1) + start;
mergeSort(nums, start, mid);
mergeSort(nums, mid + 1, end);
merge(nums, start, mid, end);
}
static void merge(int[] nums, int start, int mid, int end) {
int[] copy = new int[end - start + 1];
int i = start, j = mid + 1;// 注意i是从start开始,不是0!!!
int tail = 0;//临时数组copy的索引
while (i <= mid && j <= end) {
// 这里if包括 = 的话 保持算法稳定
if (nums[i] <= nums[j]) copy[tail++] = nums[i++];
else copy[tail++] = nums[j++];
}
while (i <= mid) copy[tail++] = nums[i++];
while (j <= end) copy[tail++] = nums[j++];
for (int k = 0; k < tail; ++k) nums[start + k] = copy[k];
}
堆排
参考:这里
// 调整start为根节点的子树
static void adjectHeap(int[] nums, int start, int length) {
int tmp = nums[start];// temp保存当前父节点
// 获得左孩子索引:child
for (int child = 2 * start + 1; child < length; child = 2 * child +1) {
// 如果有右孩子结点,并且右孩子结点的值大于左孩子结点,则选取右孩子结点
if (child + 1 < length && nums[child + 1] > nums[child]) {
child++;
}
if (tmp < nums[child]) {
nums[start] = nums[child]; // 把孩子结点的值赋给父结点
start = child; // 指针记录当前父节点引用的是谁的值
} else {
// 如果父结点的值已经>=孩子结点的最大值,则直接结束
break;
}
}
nums[start] = tmp;//在调整结束后,把父节点原来值,赋给路径上的最大孩子
}
static void heapSort(int[] nums) {
int length = nums.length;
if (length == 0) return;
// 循环建立初始最大堆
for (int i = length / 2; i >= 0; --i) {
adjectHeap(nums, i, length);
}
// // 进行n-1次循环,完成排序,依次将堆顶元素和堆尾互换,调整堆
for (int i = length - 1; i > 0; --i) {
swap(nums, 0, i);// 最后一个元素和第一元素进行交换
// 筛选 nums[0] 结点,得到i-1个结点的堆
adjectHeap(nums, 0, i);// 注意,这里是i,调整的长度截止到倒数第二个
}
}