1.冒泡排序
举例分析:假设有这么一段数组 [2,4,1,6,3,10], 从数组第一个元素i开始,与它右边的元素j相比较, 如果i>j, i和j交换位置, i++, 直到i走到了数组倒数第一个元素就结束比较
第一趟冒泡排序之后的数列为:2,1,4,3,6,10
10确定为这趟数列最大的元素, 不参与之后的比较
第二趟冒泡排序之后的序列为: 1,2,3,4,6,10
6确定为这趟数列最大的元素, 不参与之后的比较
… (直到数组第二个元素变红)
第二趟排序之后, 你会发现数组基本有序, 但是冒泡还没有走完, 计算机可没有那么聪明, 所以冒泡排序所消耗的时间很多的
代码如下
/**
* 冒泡排序
* @param arr 待排序数组
*/
static void BubblingSort(int[] arr) {
// 外层循环, 以每个元素为起点开始扫描
for (int i = 0; i < arr.length-1; i++) {
// 内层循环, 扫描每个元素的右边元素
for (int j = 0; j < arr.length-i-1; j++) {
if (arr[j] > arr[j+1]) {
Util.swap(arr, j, j+1);
}
}
}
System.out.println(Arrays.toString(arr));
}
2. 选择排序
举例分析: 例如数组{2,1,3,7,1,10,6}, 从第一个元素i开始扫描它右边的数, 找到最小元素的下标, 找到之后与i交换位置,i++, 直到i走到最后一个元素结束
与冒泡排序类似, 每一趟找到最小的元素下标,不过每一趟交换一次位置即可,所以比冒泡排序快
代码如下
/**
* 选择排序
* @param arr 待排序数组
*/
static void selectSort1(int[] arr) {
int n = 0;
// 外层循环: 从第一个元素开始为起点依次扫描它右边的元素
while (n < arr.length-1) {
// 假设第一个元素(n)为最小元素的下标
int minIndex = n;
for (int i = n+1; i < arr.length; i++) {
if (arr[minIndex] > arr[i]) {
minIndex = i; // 不断更新最小的元素的下标
}
}
// 一趟之后交换位置
Util.swap(arr,n,minIndex);
n++;
}
System.out.println(Arrays.toString(arr));
}
3. 插入排序
这个直接看图吧
或许图不够明白看以下代码希望能豁然开朗
/**
* 插入排序
* @param arr 待排序数组
*/
static void insertSort(int[] arr) {
// 从数组第一个元素开始摸牌
for (int i = 0; i < arr.length; i++) {
// 新摸到的牌
int v = arr[i];
// 原有牌中最后一张牌
int lastIndex = i-1;
while (lastIndex >= 0 && v < arr[lastIndex]) {
// lastIndex元素右移一位
arr[lastIndex+1] = arr[lastIndex];
// lastIndex下标左移一位
lastIndex--;
}
// 新摸到的牌应该待的位置
arr[lastIndex+1] = v;
}
System.out.println(Arrays.toString(arr));
}
4. 希尔排序
举例分析:例如数组{2,1,7,9,10,2,5}, 开始先对数组确定增量, 增量开始等于数组长度/2, 以增量为步数进行分组, 然后对每个组别进行插入排序, 第二次确定增量为上一次的/2,再以增量为步数进行分组插入排序, 直到增量<1(>0)
代码如下
/**
* 希尔排序
* @param arr 待排序数组
*/
static void shellSort(int[] arr) {
// 不断缩小增量
for (int interval = arr.length/2; interval > 0; interval/=2) {
for (int i = interval; i < arr.length; i++) {
// 新摸到的牌
int target = arr[i];
// 待比较的牌
int j = i-interval;
while (j >= 0 && target < arr[j]) {
arr[j+interval] = arr[j];
j -= interval;
}
arr[j+interval] = target;
}
}
System.out.println(Arrays.toString(arr));
}
5. 快速排序
快速排序算法分析:
数组a[l,r]被划分成a[l,q-1]和a[q+1,r]两个数组,使得a[q]为大小居中的数,左侧a[l,q-1]都是小于等于a[q]的元素,右侧a[q+1,r]都是大于等于a[q]的元素
伪代码如下
quickSort(a,l,r)
if(l > r)
q = partition(a,l,r)
QuickSort(a,l,q-1)
QuickSort(a,q+1,r)
5.1快速排序之单向扫描法
代码如下
/**
* 快速排序之单向扫描法
* @param arr 待排序数组
* @param l 开始下标
* @param r 结束下标
*/
private static void quickSort(int[] arr,int l, int r) {
if (l < r) {
int q = partition_1(arr, l, r);
quickSort(arr, l, q-1);
quickSort(arr, q+1, r);
}
}
private static int partition_1(int[] arr, int l, int r) {
// 定义主元
int pivot = arr[l];
// 扫描指针
int scanner = l+1;
// 右侧指针
int bigger = r;
while (scanner <= bigger) {
// 如果扫描元素小于等于主元, 扫描指针右移一位
if (arr[scanner] <= pivot) {
scanner++;
} else { // 反之右侧指针左移一位
Util.swap(arr,scanner, bigger);
bigger--;
}
}
// 主元和bigger交换位置
Util.swap(arr,l,bigger);
// 返回bigger下标
return bigger;
}
5.2快速排序之双向扫描法
代码如下
/**
* 快速排序之双向扫描法
* @param arr 待排序数组
* @param l 开始下标
* @param r 结束下标
*/
private static void quickSort(int[] arr,int l, int r) {
if (l < r) {
int q = partition_2(arr, l, r);
quickSort(arr, l, q-1);
quickSort(arr, q+1, r);
}
}
private static int partition_2(int[] arr,int l,int r) {
// 主元
int pivot = arr[l];
// 左侧指针
int left = l + 1;
// 右侧指针
int right = r;
// 当left和right交错结束循环
while (left <= right) {
// 当left(right)不断右走(左走)时, 要注意left下标会越界的情况, 所以得加上left <= right
// 左侧指针不停的往右扫描, 直到遇到第一个大于主元的就停下
while (left <= right && arr[left] <= pivot) left++;
// 右侧侧指针不停的往左扫描, 直到遇到最后一个小于等于主元的就停下
while (left <= right && arr[right] > pivot) right--;
if (left < right) {
Util.swap(arr, left, right);
}
}
// 循环结束时,主元和right交换位置
Util.swap(arr,l,right);
return right;
}
5.3快速排序之三向扫描法
代码如下
/**
* 快速排序之三相扫描法
* @param arr 待排序数组
* @param l 开始下标
* @param r 结束下标
*/
private static void quickSort(int[] arr,int l, int r) {
if (l < r) {
int q[] = partition_3(arr, l, r);
quickSort(arr, l, q[0]-1);
quickSort(arr, q[1]+1, r);
}
}
private static int[] partition_3(int[]A,int p,int r) {
int pivot = A[p]; //第一个元素设置为主元,最终所以主元左侧比全比主元小,右侧则大
int sp = p+1; //扫描指针,比主元小的开始指针--向右移动
int ep = p+1; //用于存放第一个等于主元的指针
int bigger = r; //右侧指针,比主元大的开始指针--向左移动
while(sp <= bigger) {
if(A[sp] < pivot) {//扫描指针元素小于主元
sp++;
ep++;
}
else if(A[sp] > pivot) {
//扫描指针元素大于主元,则将扫描指针和bigger指针元素交换,sp不能++
Util.swap(A,sp,bigger); //二指针的元素交换,右指针左移
bigger--;
}
else {//扫描指针元素等于主元时,要看下一扫描指针值和主元比较
sp++; //扫描指针后移
while(sp <= bigger) {//一旦扫描指针元素和主元相等,那么剩余的循环操作只在这里完成
if(A[sp] < pivot){ //下一扫描指针的元素小于主元
int t = A[sp]; //将ep和下一扫描指针元素值交换
A[sp] = A[ep];
A[ep] = t;
ep++; //ep--第一个等于主元元素指针后移,原来已用小值替换了
sp++; //扫描指针后移
}else if(A[sp] == pivot)//下一扫描指针的元素等于主元
sp++; //扫描指针后移
else 下一扫描指针的元素大于主元
{
Util.swap(A,sp,bigger); //二指针的元素交换,右指针左移
bigger--;
}
}
}
}
ep--; // ep其实是指向第一个和主元相等的指针,向前移1位,指向最后一个小于主元的元素
Util.swap(A,p,ep); //ep指向第一个小于主元的元素,将它的值和主元交换,那么ep又变成指向第一个和主元相等的指针,bigger就是我们要的q2
int[] temp = new int[2];
temp[0] = ep;
temp[1] = bigger;
return temp;
}
6. 归并排序
代码如下
// 初始化辅助空间数组helper
private static int[] helper;
/**
* 归并排序
* @param arr 待排序数组
*/
private static void mergeSort(int[] arr) {
helper = new int[arr.length];
mergeSort(arr,0,arr.length-1);
}
private static void mergeSort(int[] arr, int l, int r) {
if (l < r) {
int mid = l + ((r-l)>>1);
mergeSort(arr,l,mid);
mergeSort(arr,mid+1,r);
// 合并
merge(arr,l,mid,r);
}
}
private static void merge(int[] arr, int l, int mid, int r) {
// 拷贝数组到辅助空间数组中
System.arraycopy(arr,l,helper,l,r-l+1);
// 原始数组指针
int current = l;
// 左侧队伍开始指针
int left = l;
// 右侧队伍开始指针
int right = mid+1;
while (left <= mid && right <= r) {
if (helper[left] <= helper[right]) {
arr[current] = helper[left];
left++;
current++;
} else {
arr[current] = helper[right];
right++;
current++;
}
}
// 考虑到左侧队伍没走完
while (left <= mid) {
arr[current] = helper[left];
left++;
current++;
}
}
结语:希望大家养成多画图的习惯, 多写代码, 试试用笔和纸写出这些排序算法,你会发现自己是否真正掌握这些排序算法, 有时候并不是一定要掌握这些算法, 而是要掌握这种能力,逻辑能力和空间想象能力,思路转为代码的能力,
最后如果有看不懂的地方请谅解, 有什么问题欢迎大佬指正