排序算法
1. 冒泡排序
对升序: 从头开始两两比较,把较大的元素与较小的元素进行交换;每轮把当前最大的一个元素存入到数组当前轮次的末尾,可以理解为 每轮次都是在确定目前的最大元素。
以第一次轮为例:
- 从第一个元素开始,一一比较相邻元素,最终最大的元素在当前轮次的末尾
- 第一轮即arr[length - 1],第二轮即arr[length - 2] --> 即arr[ length - i - 1]
//冒泡排序
void bubbleSort(int arr[], int length) {
for (int i = 0; i < length; i++) {
for (int j = 0; j < length - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
int temp;
temp = arr[j + 1];
arr[j + 1] = arr[j];
arr[j] = temp;
}
}
}
}
2. 选择排序
选择排序的思想从某种程度来说,跟冒泡排序是类似的:以升序为例
- 选择排序:选出最小的元素,从前往后一一确定最小
- 冒泡排序:选出最大的元素,从后往前一一确定最大;
以第一轮为例:
- 第一个元素与其他所有数组元素比较,使得 arr[0] 存储的是最小的元素
//选择排序
void selectSort(int arr[], int length) {
for (int i = 0; i < length - 1; i++) {
for (int j = i + 1; j < length; j++) {
if (arr[i] > arr[j]) {//
int temp;
temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
}
}
3.插入排序
插入排序,一般也被称为直接插入排序。对于少量元素的排序,它是一个有效的算法。
基本思想:将一个记录插入到已经排好序的有序表中,从而形成一个新的、记录数增1的有序表。在其实现过程使用双层循环,外层循环对除了第一个元素之外的所有元素,内层循环对当前元素前面有序表进行待插入位置查找,并进行移动。
void insertSort(int arr[], int length) {
for (int i = 1; i < length; i++) {// arr[i] 为当前要插入的元素
int j;
//该层循环是为找到比arr[i]小的下标j,那么j + 1 就是 arr[i] 要插入的下标位置
for (j = i - 1; j >= 0 && arr[i] < arr[j]; j--) { //arr[0, i - 1]是已经排好序的有序表
arr[j + 1] = arr[j];//实现比 arr[i] 大的数往后一位
}
arr[j + 1] = arr[i];
}
}
4.希尔排序
希尔排序(Shell’s Sort)是插入排序的一种又称“缩小增量排序”,是插入排序算法的一种更效的改进版本,实质上是一种分组插入方法。
希尔排序是把数组按下标的一定增量分组,对每组使用插入排序算法排序;
随着增量逐渐减少,每组包含的元素越来越多,当增量减至 1 时,整个文件恰被分成一组,算法便终止。
以第一轮为例:假设 n 为6
- 增量为 n / 2 = 3 ;分为 3 组
- 以数组下标代表分组 : [ 0, 3] ; [1, 4] ; [2, 5]
void incrementSort(int arr[], int length) {
int n = length;
while (true) {
n /= 2; //增量每次减半
for (int i = 0; i < n; i++) {
for (int j = i + n; j < length; j += n) {//这个循环里其实就是一个插入排序
int k = j - n;
while (k >= 0 && arr[k] > arr[k + n]) {
int temp = arr[k];
arr[k] = arr[k + n];
arr[k + n] = temp;
k -= n;
}
}
}
if (n == 1)
break;
}
}
5.快速排序
快速排序算法通过多次比较和交换来实现排序,其排序流程如下:
-
首先选定一个记录(通常选取第一个记录),通过该记录的值将数组分成左右两部分
-
将大于或等于记录值的数据集中到数组右边,小于记录值的数据集中到数组的左边。
-
左边和右边的数据继续进行排序。对于左侧的数组数据,又可以取一个分界值,将该部分数据分成左右两部分,同样在左边放置较小值,右边放置较大值。右侧的数组数据也可以做类似处理。
这可以说是一个递归过程。通过递归将左侧部分排好序后,再递归排好右侧部分的顺序。当左、右两个部分各数据排序完成后,整个数组的排序也就完成了
// 快速排序
void QuickSort(int arr[], int start, int end)
{
if (start >= end)
return;
int i = start;
int j = end;
// 以第一个记录为基准
int base = arr[start];
while (i < j)
{
// 从右向左找比基准数小的数
while (j > i && arr[j] >= base)
j--;
if (i < j)//说明右边有arr[j] < base
{
arr[i] = arr[j];//将该元素放于左边
i++;//i下标已确定比 base 小;从左向右扫描的下标开始值 + 1
}
// 从左向右找比基准数大的数
while (i < j && arr[i] < base)
i++;
if (i < j)//说明有arr[i] >= base
{
arr[j] = arr[i];//将该元素放于右边
j--;//j下标已确定不小于 base ;下次 从右向左扫描的下标结尾 - 1
}
}
// 把基准数放到i的位置
arr[i] = base;
// 递归
QuickSort(arr, start, i - 1);
QuickSort(arr, i + 1, end);
}
6. 归并排序
归并排序即是将两个本就有序的序列合并成一个有序的序列。
// 归并排序
void MergeSort(int[] arr, int[] nums)
{
int len1 = arr.length;
int len2 = nums.length;
int[] newArr = new int[len1 + len2];//新的有序序列
int i = 0, j = 0,k = 0;
while(i < len1 && j < len2){
if(arr[i] <= nums[j]){
newArr[k] = arr[i];
i++;//使 i 指向下一个将与nums[j]的元素
}else if(arr[i] > nums[j]){
newArr[k] = nums[j];
j++;//使 j 指向下一个将与arr[i]的元素
}
k++;
}
while (i < len1){
newArr[k] = arr[i];
k++;
i++;
}
while (j < len2){
newArr[k] = nums[j];
k++;
j++;
}
}
7. 堆排序
堆是一个近似的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点可以将堆看做是一个完全二叉树。
- 每个结点的值都大于等于其左右孩子结点的值,称为大顶堆
- 每个结点的值都小于等于其左右孩子结点的值,称为小顶堆
基本思想为:将待排序列构造成一个大顶堆(小顶堆),整个序列的最大值(最小值)就是堆顶的根结点,将根节点的值和堆数组的末尾元素交换,此时末尾元素就是最大值(最小值),然后将剩余的n-1个序列重新构造成一个堆,这样就会得到n个元素中的次大值(次小值),如此反复执行,最终得到一个有序序列。
以大顶堆为例:如何将二叉树调整为堆
- 从二叉树最后一个非叶子结点开始,将结点上的值依次和左右子树的值作比较,若子树上的值大于结点值,则将其交换。
- 对二叉树上所有的非叶子结点执行上述操作
public static int[] heapSort(int[] array) {
//索引从0开始的,最后一个非叶子结点array.length/2 - 1
for (int i = array.length / 2 - 1; i >= 0; i--) {
adjustHeap(array, i, array.length); //调整堆
}
// 开始排序
for (int j = array.length - 1; j > 0; j--) {
// 把大顶堆的根元素,放到数组的最后;即每一次的堆调整之后,都会有一个元素到达自己的最终位置
swap(array, 0, j);
// 最后一个元素无需再考虑排序问题了。
adjustHeap(array, 0, j);// 重新调整堆,获取当前最大值
}
return array;
}
public static void adjustHeap(int[] array, int i, int length) {
int temp = array[i];//非叶子节点
for (int k = 2 * i + 1; k < length; k = 2 * k + 1) {
//2*i+1 为 i 的左子树 ,2*k+1为 k 的左子树
// 让k先指向子节点中最大的节点
if (k + 1 < length && array[k] < array[k + 1]) { //如果有右子树,并且右子树大于左子树
k++;
}
//如果发现结点(左右子结点)大于根结点,则进行值的交换
if (array[k] > temp) {
swap(array, i, k);
// 如果子节点更换了,以子节点为根的子树会受到影响,所以,循环对子节点所在的树继续进行判断
i = k;
} else { //不用交换,直接终止循环
break;
}
}
}
public static void swap(int[] arr, int a, int b) {
int temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
为根的子树会受到影响,所以,循环对子节点所在的树继续进行判断
i = k;
} else { //不用交换,直接终止循环
break;
}
}
}
public static void swap(int[] arr, int a, int b) {
int temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}