插入排序
插入排序原理
把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列。
代码实现:
public static void insertSort(int[] array) {
// bound 变量来把整个数组分成两个区间
// [0, bound) 已排序区间
// [bound, size) 待排序区间
for (int bound = 1; bound < array.length; bound++) {
// bound 下标对应的元素就是待插入元素.
// 把这个元素放到前面的有序顺序表中的合适位置
int tmp = array[bound];
int cur = bound - 1;
for (; cur >= 0; cur--) {
if (array[cur] > tmp) {
array[cur + 1] = array[cur];
} else {
break;
}
}
array[cur + 1] = tmp;
}
}
希尔排序
工作原理:
希尔排序法又称缩小增量法。希尔排序法的基本思想是:先选定一个整数,把待排序文件中所有记录分成个组,所有 距离为的记录分在同一组内,并对每一组内的记录进行排序。然后,取,重复上述分组和排序的工作。当到达gap=1时, 所有记录在统一组内排好序。
- 希尔排序是对直接插入排序的优化。
- 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就会很 快。这样整体而言,可以达到优化的效果。
代码实现:
public static void shellSort(int[] array) {
int gap = array.length;
while (gap > 1) {
insertSortGap(array,gap);
gap = gap / 2;
}
insertSortGap(array,1);
}
private static void insertSortGap(int[] array, int gap) {
int bound = 0;
for (; bound < array.length; bound++) {
int tmp = array[bound];
int cur = bound -gap;
for (;cur >= 0;cur -= gap) {
if (array[cur] > tmp) {
array[cur + gap] = array[cur];
} else {
break;
}
}
array[cur + gap] = tmp;
}
}
直接选择排序
工作原理:
每一次从无序区间选出最大(或最小)的一个元素,存放在无序区间的最后(或最前),直到全部待排序的数据元素排完。
代码实现:
public static void selectSort(int[] array) {
//bound表示边界
//选择未排序的最大放入未排序的末尾
//[0,bound)已排序
//[bound,size)未排序
for (int bound = 0; bound < array.length; bound++) {
for (int cur = bound + 1 ; cur < array.length; cur++) {
if (array[bound] > array[cur]) {
swap(array,cur,bound);
}
}
}
}
private static void swap(int[] array, int x, int y) {
int tmp = array[x];
array[x] = array[y];
array[y] = tmp;
}
堆排序
基本原理也是选择排序,只是不在使用遍历的方式查找无序区间的最大的数,而是通过堆来选择无序区间的最大的
数。
注意: 排升序要建大堆,排降序要建小堆
代码实现:(递归实现)
public static void heapSort(int[] array) {
//先建堆
creatHeap(array);
//取出最大的放在数组最后面
for (int index = 0; index < array.length; index++) {
//已排序区间[array.length - i, array.length]
swap(array,0,array.length - index - 1);
// 第一个参数是数组
// 第二个参数是数组中的有效元素的个数
// 第三个参数是从哪个位置进行向下调整
ShiftDown(array,array.length - index - 1, 0);
}
}
private static void creatHeap(int[] array) {
//最后一个非叶子节点开始构建
for (int i = array.length - 1 - 1 / 2; i >= 0; i--) {
ShiftDown(array,array.length,i);
}
}
private static void ShiftDown(int[] array, int size, int index) {
int parent = index;
int child = 2 * parent + 1;
while (child < size) {
if (child + 1 < size &&
array[child + 1] > array[child]) {
child = child + 1;
}
//child 一定是左右孩子中最大的
if (array[parent] < array[child]) {
swap(array,parent,child);
} else {
break;
}
//传递下标
parent = child;
child = 2 * parent + 1;
}
}
冒泡排序
工作原理:
在无序区间,通过相邻数的比较,将最大的数冒泡到无序区间的最后,持续这个过程,直到数组整体有序。
代码实现:
public static void bubbleSort(int[] array) {
//[0,bound)已排序区间
//[bound,size)未排序区间
for (int bound = 0; bound < array.length; bound++) {
for (int index = array.length - 1; index > bound; index--) {
if (array[index - 1] > array[index]) {
swap(array,index - 1,index);
}
}
}
}
快速排序
工作原理:
-
从待排序区间选择一个数,作为基准值(pos)。
-
Partition: 遍历整个待排序区间,将比基准值小的(可以包含相等的)放到基准值的左边,将比基准值大的(可以包含相等的)放到基准值的右边。
-
采用分治思想,对左右两个小区间按照同样的方式处理,直到小区间的长度 == 1,代表已经有序,或者小区间的长度 == 0,代表没有数据。
代码实现:
public static void quickSort(int[] array) {
quickSortHelper(array,0,array.length - 1);
}
private static void quickSortHelper(int[] array, int left, int right) {
if (left >= right) {
//数组中只有一个元素,或者没有元素,不需要排序
return;
}
int pos = partition(array,left,right);
quickSortHelper(array,left,pos - 1);
quickSortHelper(array,pos + 1,right);
}
private static int partition(int[] array, int left, int right) {
int baseIndex = left;
int baseValue = array[baseIndex];
while (left < right) {
while (left < right && array[right] >= baseValue) {
right--;
}
while (left < right && array[left] <= baseValue) {
left++;
}
swap(array,left,right);
}
swap(array,left,baseIndex);
return left;
}
非递归实现
public static void quickSortByLoop(int[] array) {
//在栈中存取待处理的下标
Stack<Integer> stack = new Stack<>();
stack.push(array.length - 1);
stack.push(0);
while (!stack.isEmpty()) {
int left = stack.pop();
int right = stack.pop();
if (left >= right) {
continue;
}
int pos = partition(array,left,right);
stack.push(pos - 1);
stack.push(left);
stack.push(right);
stack.push(pos + 1);
}
}
private static int partition(int[] array, int left, int right) {
int baseIndex = left;
int baseValue = array[baseIndex];
while (left < right) {
while (left < right && array[right] >= baseValue) {
right--;
}
while (left < right && array[left] <= baseValue) {
left++;
}
swap(array,left,right);
}
swap(array,left,baseIndex);
return left;
}
归并排序
工作原理:
归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子 序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
代码实现:
public static void mergeSort(int[] array) {
// 后两个参数表示要进行归并排序的区间.
// [0, array.length)
// new 足够大的数组, 把这个数组作为缓冲区传给
// 递归函数
mergeSortHelper(array, 0, array.length);
}
private static void mergeSortHelper(int[] array, int left, int right) {
// [left, right) 构成了要去进行归并排序的区间
// 如果区间为空区间, 或者只有一个元素, 都不用排序
if (left >= right || right - left == 1) {
// 空区间或者区间只有一个元素, 都不需要进行归并排序
return;
}
// 使用类似后序遍历的方式.
// 先把当前的待排序区间拆成两半,
// 递归的对这两个子区间进行归并排序, 保证两个区间有序之后
// 再进行合并
int mid = (left + right) / 2;
// [left, mid)
// [mid, right)
mergeSortHelper(array, left, mid);
mergeSortHelper(array, mid, right);
merge(array, left, mid, right);
}
private static void merge(int[] array, int left,
int mid, int right) {
// 创建一段临时空间辅助进行归并
// 这个临时空间的长度应该是两个待归并区间的长度之和
int length = right - left;
int[] output = new int[length];
// 这个变量保存着当前 output 中的末尾元素的下标
int outputIndex = 0;
// i 和 j 是用来遍历两个区间的辅助变量
// [left, mid)
// [mid, right)
int i = left;
int j = mid;
while (i < mid && j < right) {
// 此处的 if 条件必须要 <= , 否则没法保证稳定性
if (array[i] <= array[j]) {
// i 对应的元素比 j 小
// 就把 i 对应的元素插入到 output 末尾
output[outputIndex++] = array[i++];
} else {
output[outputIndex++] = array[j++];
}
}
// 上面的循环结束之后, 两个区间至少有一个是遍历完了的.
// 就把剩下的区间的内容直接拷贝到 output 中即可.
while (i < mid) {
output[outputIndex++] = array[i++];
}
while (j < right) {
output[outputIndex++] = array[j++];
}
// 最后一步, 把 output 中的元素拷贝回原来的区间
for (int k = 0; k < length; k++) {
array[left + k] = output[k];
}
}
非递归实现:
public static void mergeSortByLoop(int[] array) {
// 借助下标相关的规律来进行分组.
// 初始情况下, 每个元素单独作为一组
// [0] [1] [2] [3] [4] [5]
// [0, 1] 和 [2, 3] 合并. [4, 5] 和 [6, 7] 区间合并
// [0, 1, 2, 3] [4, 5, 6, 7]
for (int gap = 1; gap < array.length; gap *= 2) {
for (int i = 0; i < array.length; i += 2 * gap) {
// 这个循环负责在 gap 为指定值的情况下
// 把所有的区间进行归并
// 针对当前的 i, 也能划分出两个需要进行归并的区间
// [beg, mid)
// [mid, end)
int beg = i;
int mid = i + gap;
int end = i + 2 * gap;
if (mid > array.length) {
mid = array.length;
}
if (end > array.length) {
end = array.length;
}
merge(array, beg, mid, end);
}
}
}
private static void merge(int[] array, int left,
int mid, int right) {
// 创建一段临时空间辅助进行归并
// 这个临时空间的长度应该是两个待归并区间的长度之和
int length = right - left;
int[] output = new int[length];
// 这个变量保存着当前 output 中的末尾元素的下标
int outputIndex = 0;
// i 和 j 是用来遍历两个区间的辅助变量
// [left, mid)
// [mid, right)
int i = left;
int j = mid;
while (i < mid && j < right) {
// 此处的 if 条件必须要 <= , 否则没法保证稳定性
if (array[i] <= array[j]) {
// i 对应的元素比 j 小
// 就把 i 对应的元素插入到 output 末尾
output[outputIndex++] = array[i++];
} else {
output[outputIndex++] = array[j++];
}
}
// 上面的循环结束之后, 两个区间至少有一个是遍历完了的.
// 就把剩下的区间的内容直接拷贝到 output 中即可.
while (i < mid) {
output[outputIndex++] = array[i++];
}
while (j < right) {
output[outputIndex++] = array[j++];
}
// 最后一步, 把 output 中的元素拷贝回原来的区间
for (int k = 0; k < length; k++) {
array[left + k] = output[k];
}
}