一、冒泡排序
/*
冒泡排序:依次选择第i位置上可选的最小数
[7,2,5,3,4] for j in [i+1->n] compare nums[i]和nums[j]
i=0 | [7,2,5,3,4](2,7,5,3,4) => [2,7,5,3,4] => [2,7,5,3,4] => [2,7,5,3,4]
i ^ i ^ i ^ i ^
i=1 | [2,7,5,3,8](2,5,7,3,4) => [2,5,7,3,8](2,3,7,5,4) => [2,3,7,5,4]
i ^ i ^ i ^
i=2 | [2,3,7,5,4](2,3,5,7,4) => [2,3,5,7,4](2,3,4,7,5)
i ^ i ^
i=3 | [2,3,4,7,5](2,3,4,5,7)
i ^
*/
public static void bubbleSort(int[] nums) {
int n = nums.length;
for (int i = 0; i < n - 1; i++) {
for (int j = i + 1; j < n; j++) {
if (nums[j] < nums[i]) {
swap(nums, i, j);
}
}
}
}
private static void swap(int[] nums, int i, int j) {
int tmp = nums[i];
nums[i] = nums[j];
nums[j] = tmp;
}
二、插入排序
/*
插入排序:将数组划分成两个子区间 L[1..i-1](有序区)和 R[i..n](无序区)
插入排序与打扑克时整理手上的牌非常类似
[7,2,5,3,4] for i in [1,n] compare nums[i]和nums[left]
目的:找到比最后一个nums[i]大的位置!!!该位置就是nums[i]的应有的座次
i=1 tmp=nums[1]=2 | [7,2,5,3,4]->(7,7,5,3,4) --结束循环--> [2,7,5,3,4] 临时值
^ i
i=2 tmp=nums[2]=5 | [2,7,5,3,4]->(2,7,7,3,4) => [2,7,7,3,4] --结束循环--> [2,5,7,3,4] 2的下一个元素7就是最后一个比tmp小的位置,后同理
^ i ^
i=3 tmp=nums[3]=3 | [2,5,7,3,4]->(2,5,7,7,4) => [2,5,7,7,4]->(2,5,5,7,4) => [2,5,5,7,4] --结束循环--> [2,3,5,7,4]
^ i ^ i ^ i
i=4 tmp=nums[4]=4 | [2,3,5,7,4]->(2,3,5,7,7) => [2,3,5,7,7]->(2,3,5,5,7) => [2,3,5,5,7] --结束循环--> [2,3,4,5,7]
^ i ^ i ^ i
*/
public static void insertSort(int[] nums) {
int n = nums.length;
int tmp;
for (int i = 1; i < n; i++) {
tmp = nums[i];
int left = i - 1;
while (left >= 0) {
// System.out.println(String.format("nums[%s]=%s nums[%s]=%s", i, tmp, left, nums[left]));
if (tmp < nums[left]) { // 比待插入的数大 继续左移
nums[left + 1] = nums[left];
left--;
} else { // left及左面元素都比tmp小,跳出循环
break;
}
}
// 此时left=-1 || left为第一个不大于tmp的位置。left+1为最后一个小于tmp的位置
nums[left + 1] = tmp;
}
}
三、快速排序
public static void quickSort(int[] nums, int left, int right) {
if (left < right) {
int p = partition(nums, left, right);
quickSort(nums, left, p - 1);
quickSort(nums, p + 1, right);
}
}
/*
快速排序:采用算法导论中的划分元方法
[7,2,5,3,4] => [7,2,5,3,4]->[2,7,5,3,4] => [2,7,5,3,4] => [2,7,5,3,4]-> [2,3,5,7,4] --循环结束,交换划分元位置--> [2,3,4,7,5] ==>子数组 [2,3] && [7,5]
x=4 i j i j i j i j
[2,3] => [2, 3] --循环结束,交换划分元位置--> [2,3] ==> 子数组 [2]
x=3 i j i(j)
[7,5] => [5, 7] --循环结束,交换划分元位置--> [5,7] ==> 子数组 [5]
x=5 i j i(j)
*/
private static int partition(int[] nums, int left, int right) {
int i = left - 1;
int x = nums[right]; // 划分元,一般选择末尾元素
for (int j = left; j < right; j++) { // for j in [left, right - 1]
if (nums[j] <= x) { // 保障i以及左面的元素都比划分元小
i++;
swap(nums, i, j);
}
}
// 最后循环结束,交换划分元的位置,保障划分元左侧元素小于等于自己,右侧元素大于自己
swap(nums, i + 1, right);
// 返回划分元的位置
return i + 1;
}
private static void swap(int[] nums, int i, int j) {
int tmp = nums[i];
nums[i] = nums[j];
nums[j] = tmp;
}
四、归并排序
/*
归并排序
1、利用递归,不断得寻找左子数组和右子数组,一直到数组的长度为一
2、非原址排序,新创建一个临时数组
->[] 标识从上一步拆分而来
[]-> 表示下一步归并成新的结果
[7,2,5,3,4]
|------>[7,2,5]
| |-------->[7,2]
| | |------>[7]->
| | | |--->[2,7]->
| | |------>[2]-> |
| | |---->[2,5,7]->
| |-------->[5]-----------------------> |
| |--->[2,3,4,5,7]
|------->[3,4] |
|-------->[3]-----------> |
| |--->[3,4]---------------->
|-------->[4]----------->
*/
public static void mergeSort(int[] nums, int left, int right) {
if (left < right) {
int mid = (left + right) / 2;
mergeSort(nums, left, mid);
mergeSort(nums, mid + 1, right);
merge(nums, left, mid, right);
}
}
private static void merge(int[] nums, int left, int mid, int right) {
// 辅助数组
int[] tmp = new int[nums.length];
int i = left;
int p1 = left, p2 = mid + 1;
while (p1 <= mid && p2 <= right) {
if (nums[p1] < nums[p2]) {
tmp[i++] = nums[p1++];
} else {
tmp[i++] = nums[p2++];
}
}
// 只有一边数组存在未遍历元素
while (p1 <= mid) {
tmp[i++] = nums[p1++];
}
while (p2 <= right) {
tmp[i++] = nums[p2++];
}
// 拷贝到原数组
for (int j = left; j <= right; j++) {
nums[j] = tmp[j];
}
}
五、堆排序
/*
堆排序
[7,2,5,3,4]
7(0)
/ \
2(1) 5(2)
/ \
3(3) 4(4)
leftChild=2*i+1 && rightChild=2*i+2
parent=(i-1)/2
以大根堆为例
思路说明:排序的核心分为
1、整堆(一次调整以某个节点为根时堆的顺序,比如下面调整其局部结构为:
2(current) 4
/ \ => / \
3 4 3 2(current)
2、建堆(初始化建堆,即从第一个非叶子节点(叶子节点本身符合大堆特性)倒序开始 整堆,直到调整完最后一个非叶子节点;此时符合大根堆属性)
3、交换堆顶元素和堆最后一个元素。将堆分为 [有序] 和 [无序] 两个部分
1、初始化建堆
2、取堆顶元素和堆最后一个元素交换。
初始时 无序=[1,2..n] 有序=[] ===> 无序[1,2...n-1] 有序[n]
3、步骤2以后,无序堆的大小-1,且新的堆可能不符合大根堆属性(但其左右孩子仍然为大根堆)
所以直接从位置0重新整堆,即整体调整为大根堆。
4、重复步骤2和3直到无序堆大小为1(剩余的最小的元素)。此时全部有序
*/
public static void heapSort(int[] nums) {
// 初始化的堆大小可以是数组长度,此处我选择的定义为无序堆的最后一个下标
int heapSize = nums.length - 1;
buildHeap(nums, heapSize);
while (heapSize >= 1) {
swap(nums, 0, heapSize); // 将堆顶元素和堆最后一个元素交换。无序堆大小-1
// 此时根节点的左右孩子依然保持大(小)根堆的特性
heapSize--;
adjustHeap(nums, heapSize, 0);
}
}
// 建堆
private static void buildHeap(int[] nums, int heapSize) {
// 最后一个父节点 (叶子节点本身就是个堆)
int parent = getParent(heapSize);
// 初始化调整堆 针对每一个非叶子节点倒序
for (int i = parent; i >= 0; i--) {
adjustHeap(nums, heapSize, i);
}
}
// 整堆
private static void adjustHeap(int[] nums, int heapSize, int i) {
int left = getLeftChild(i);
int right = getRightChild(i);
// 记录自身节点和左右孩子最大值的位置
int largest = i;
// 取左右孩子的最大值位置
if (left <= heapSize && nums[left] > nums[largest]) {
largest = left;
}
if (right <= heapSize && nums[right] > nums[largest]) {
largest = right;
}
if (largest != i) { // 自身并非最大值处,自左孩子或者右孩子继续向下整堆
swap(nums, i, largest);
adjustHeap(nums, heapSize, largest);
}
}
private static int getLeftChild(int i) {
return 2 * i + 1;
}
private static int getRightChild(int i) {
return 2 * i + 2;
}
private static int getParent(int i) {
return (i - 1) / 2;
}