2 十大经典排序算法
清华架构师马士兵老师呕心沥血打造算法与数据结构(十大经典排序算法)全套视频教程:https://www.bilibili.com/video/BV1Wq4y1Z7yi/?spm_id_from=333.337.search-card.all.click
0 前言
【1预告】
为什么讲这门课?
面试我最强
你值得拥有
什么人可以听这门课?
逻辑能力
任何一门语言基础,最好是java、c++、c中的一种
什么人应该听?
需要做面试题
想进大厂
提升内功
【2算法基本概念】
什么是数据结构?
Data Structure
存储数据的不同方式
什么是算法?
同一问题的不同的解决方法
算法往往是针对特定数据结构的
如何测算算法的优劣?
时间测算,1.计算算法时间差,2.幅度不够循环来凑
空间测算
Big O
时间-问题(数据)规模:
1.不考虑必须要做的操作,例如:循环、赋初值、程序初始化
2.不考虑常数项,例如:2n -》 n
3.不考虑低次项,例如:n2+n -》 n2
O:
1.时间 -》 规模
2.用计算时间差的方式测算
3.数组、链表
【3排序算法-宋词记忆法】
重要的排序:插入排序、堆排序、归并排序、快速排序
《忆排序 面试我最强》
选泡插,
快归堆希桶计基,
恩方恩老恩一三,
对恩加K恩乘K,
不稳稳稳不稳稳,
不稳不稳稳稳稳。
【4如何写算法程序】
1.由简单到复杂
验证一步走一步
多打印中间结果
2.先局部后整体
没思路时先细分
3.先粗糙后精细
变量更名
语句合并
边界处理
1 选择排序
【选择排序】
package com.liweixiao;
import static com.liweixiao.UtilTools.print;
import static com.liweixiao.UtilTools.swap;
/**
* @author:LiWeixiao
* @date:2023/3/6
* @description:选择排序
*/
public class SelectionSort {
public static void main(String[] args) {
int[] arr={5,3,6,8,1,7,9,10,2,4};
for (int i = 0; i < arr.length-1; i++) {
int minPos=i;
for (int j = i+1; j < arr.length; j++) {
minPos= arr[j]<arr[minPos] ? j : minPos;
}
swap(arr,i,minPos);
System.out.println("\n经过第"+i+"轮循环:");
print(arr);
}
}
public static void sort(int[] arr) {
for (int i = 0; i < arr.length-1; i++) {
int minPos=i;
for (int j = i+1; j < arr.length; j++) {
minPos= arr[j]<arr[minPos] ? j : minPos;
}
swap(arr,i,minPos);
}
}
}
【常用工具】
package com.liweixiao;
/**
* @author:LiWeixiao
* @date:2023/3/6
* @description:常用工具
*/
public class UtilTools {
//循环打印方法
static void print(int[] arr){
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]+" ");
}
}
//交换方法
static void swap(int[] arr,int i,int j){
int temp=arr[i];
arr[i]=arr[j];
arr[j]=temp;
}
}
【验证算法-对数器】
如何验算你的算法是否正确?
1.肉眼观察
2.产生足够多随机样本
3.用确定正确的算法计算样本结果
4.对比被验证算法的结果
package com.liweixiao;
import java.util.Arrays;
import java.util.Random;
/**
* @author:LiWeixiao
* @date:2023/3/6
* @description:对数器
*/
public class DataChecker {
//随机生成数组
static int[] generateRandomArray(){
Random r=new Random();
int[] arr=new int[10000];
for (int i = 0; i < arr.length; i++) {
arr[i]=r.nextInt(10000);
}
return arr;
}
//对数
static void check(){
/*Arrays.sort耗时4
选择排序耗时37
冒泡排序耗时135
插入排序耗时73
希尔排序耗时196
Knuth排序耗时109*/
boolean same=true;
for (int times = 0; times < 1000; times++) {
int[] arr1 = generateRandomArray();
int[] arr2=new int[arr1.length];
System.arraycopy(arr1,0,arr2,0,arr1.length);
Arrays.sort(arr1);
//SelectionSort.sort(arr2);
//BubbleSort.sort(arr2);
//InsertionSort.sort(arr2);
//ShellSort.sort(arr2);
//ShellSort.sortKnuth(arr2);
//MergeSort.sort(arr2,0,arr2.length-1);
QuickSort.sort(arr2,0,arr2.length-1);
for (int i = 0; i < arr2.length; i++) {
if(arr1[i] != arr2[i]) {
same=false;
}
}
}
System.out.println(same == true ? "right" : "wrong");
}
public static void main(String[] args) {
check();
}
}
2 冒泡排序
package com.liweixiao;
import static com.liweixiao.UtilTools.print;
import static com.liweixiao.UtilTools.swap;
/**
* @author:LiWeixiao
* @date:2023/3/6
* @description:冒泡排序
*/
public class BubbleSort {
public static void main(String[] args) {
int[] arr={9,3,1,4,6,8,7,10,5,2};
sort(arr);
print(arr);
}
static void sort(int[] arr){
for (int i = 0; i < arr.length; i++) {
boolean flag=true;
for (int j = 0; j < arr.length-1-i; j++) {
if(arr[j]>arr[j+1]){
swap(arr,j,j+1);
flag=false;
}
}
if(flag){
break;
}
}
}
}
3 插入排序【重要】
package com.liweixiao;
/**
* @author:LiWeixiao
* @date:2023/3/6
* @description:插入排序
*/
public class InsertionSort {
public static void main(String[] args) {
int[] arr={9,3,1,4,6,8,7,10,5,2};
sort(arr);
UtilTools.print(arr);
}
static void sort(int[] arr) {
for (int i = 1; i < arr.length; i++) {
for (int j = i; j > 0; j--) {
if(arr[j]<arr[j-1]){
UtilTools.swap(arr,j,j-1);
}
}
}
}
}
【简单排序算法总结】
冒泡,基本不用,太慢
选择,基本不用,不稳
插入,样本小且基本有序的时候效率比较高
4 堆排序【重要】
二叉树,大顶堆,小顶堆
大顶堆:每个节点的值都大于或等于其子节点的值,在堆排序算法中用于升序排列。
https://www.bilibili.com/video/BV1fp4y1D7cj/?spm_id_from=333.337.search-card.all.click
下标为i的节点的父节点下标:(i-1)/2【整数除法】
下标为i的节点的左孩子下标:i2+1
下标为i的节点的右孩子下标:i2+2
步骤:
1.建堆
2.排序,最后一个和第一个交换(最大),最后一个变成最大的,去掉最后一个,重复1、2
package com.liweixiao;
/**
* @author:LiWeixiao
* @date:2023/3/8
* @description:堆排序
*/
public class HeapSort {
public static void main(String[] args) {
int[] arr={9,3,1,4,6,8,7,10,5,2};
sort(arr);
UtilTools.print(arr);
}
//堆排序
static void sort(int[] arr){
for (int i = arr.length / 2 - 1; i >= 0; i--) {
heapify(arr,i,arr.length);
}
for (int j = arr.length-1; j > 0; j--) {
int temp=arr[j];
arr[j]=arr[0];
arr[0]=temp;
heapify(arr,0,j);
}
}
//建堆
static void heapify(int[] arr,int i,int length){
int temp=arr[i];
for(int j=i*2+1;j<length;j=j*2+1){
if(j+1<length && arr[j]<arr[j+1]){
j++;
}
if(arr[j]>temp){
arr[i]=arr[j];
i=j;
}else {
break;
}
}
arr[i]=temp;
}
}
5 希尔排序
D.L Shell
间隔,改进的插入排序
package com.liweixiao;
/**
* @author:LiWeixiao
* @date:2023/3/6
* @description:希尔排序
*/
public class ShellSort {
public static void main(String[] args) {
int[] arr={9,3,1,4,6,8,7,10,5,2};
sort(arr);
UtilTools.print(arr);
}
static void sort(int[] arr){
for (int gap=arr.length/2; gap >0 ; gap /=2) {
for (int i = gap; i < arr.length; i++) {
for (int j = i; j > gap-1; j-=gap) {
if(arr[j]<arr[j-gap]){
UtilTools.swap(arr,j,j-gap);
}
}
}
}
}
}
【Knuth排序】
唐纳德·克努特Donald Ervin Knuth
h=1
h=3*h+1
static void sortKnuth(int[] arr){
int h=1;
while (h <= arr.length/3){
h=3*h+1;
}
for (int gap=h; gap >0 ; gap =(gap-1)/3) {
for (int i = gap; i < arr.length; i++) {
for (int j = i; j > gap-1; j-=gap) {
if(arr[j]<arr[j-gap]){
UtilTools.swap(arr,j,j-gap);
}
}
}
}
}
6 归并排序【重要】
【1递归】方法套方法
package com.liweixiao;
/**
* @author:LiWeixiao
* @date:2023/3/7
* @description:递归算法
*/
public class Test {
public static void main(String[] args) {
System.out.println(f(10));
}
static int f(int i){
if(i<1){return 0;}
if(i==1){return 1;}
return i+f(i-1);
}
}
【2归并排序】
步骤:
1.分成最小单元进行比较
2.合并算法,2个指针
3.最后剩余的数组,复制
package com.liweixiao;
/**
* @author:LiWeixiao
* @date:2023/3/7
* @description:归并排序
*/
public class MergeSort {
public static void main(String[] args) {
int[] arr={9,3,1,4,6,8,7,10,5,2};
sort(arr,0,arr.length-1);
UtilTools.print(arr);
}
static void sort(int[] arr,int left,int right){
if(left==right){ return;}
//分成两半
int mid=left+(right-left)/2;
//左边排序
sort(arr,left,mid);
//右边排序
sort(arr,mid+1,right);
merge(arr,left,mid+1,right);
}
static void merge(int[] arr,int leftPtr,int rightPtr,int rightBound){
int mid=rightPtr-1;
int[] temp=new int[rightBound-leftPtr+1];
int i=leftPtr;
int j=rightPtr;
int k=0;
while (i<=mid && j<=rightBound){
temp[k++] =arr[i] <= arr[j] ? arr[i++] : arr[j++];
}
while (i<=mid){ temp[k++]=arr[i++]; }
while (j<=rightBound) {temp[k++]=arr[j++];}
for (int m = 0; m < temp.length; m++) {
arr[leftPtr+m]=temp[m];
}
}
}
【3】java对象排序,多种语言应用
快速排序不稳定,对象排序一般要求稳定。
Arrays.sort,基本数据类型数组使用的是双轴快速排序,对象使用的是TimSort排序。
TimSort是一个混合、稳定的排序算法,mergeSort归并排序和binarySort二分插入排序算法的混合体。
7 快速排序【重要】
【快速排序】单轴,选1个数
第1种,搞个小区,扩展,大数和小数交换
第2种,搞个小区和大区,2端同时扩展
第3种,两边同时找,找到后大数和小数直接交换,最后选择数和插入位置数交换
本文采用第三种
上图有问题
package com.liweixiao;
/**
* @author:LiWeixiao
* @date:2023/3/7
* @description:快速排序
*/
public class QuickSort {
public static void main(String[] args) {
int[] arr={9,3,1,4,6,8,7,10,5,2};
sort(arr,0,arr.length-1);
UtilTools.print(arr);
}
static void sort(int[] arr,int leftBound,int rightBound){
if(leftBound >= rightBound) {return;}
int mid=partition(arr,leftBound,rightBound);
sort(arr,leftBound,mid-1);
sort(arr,mid+1,rightBound);
}
static int partition(int[] arr,int leftBound,int rightBound){
int pivot=arr[rightBound];
int left=leftBound;
int right=rightBound-1;
while (left<=right){
while (left<=right && arr[left]<=pivot){left++;}
while (left<=right && arr[right]>pivot){right--;}
if(left<right){
UtilTools.swap(arr,left,right);
}
}
UtilTools.swap(arr,left,rightBound);
return left;
}
}
【调试Bug】
1.通读程序
2.输出中间值
3.剪功能,定位小功能
4.再加功能
5.最后的办法,对照正确的代码,对编码能力没有提升
【快排改进】
双轴快排,选2个数
第一个区域放小于小数,第二个区域放大于等于小数 小于等于大数,第三个区域放大于大数
【Arrays.sort】
pair insertion sort双插入排序
源码比较复杂
35岁干什么,成为大牛;成不了大牛,至少是一小片的小牛。
8 计数排序
非比较排序,桶思想的一种。
【算法思想】
量大但是范围小。例如:某大型企业数万人员工年龄排序;如何快速得知高考名次(腾讯面试)。
package com.liweixiao;
import java.util.Arrays;
/**
* @author:LiWeixiao
* @date:2023/3/8
* @description:计数排序
*/
public class CountSort {
public static void main(String[] args) {
int[] arr={9,3,1,4,6,8,7,0,5,2,3,1,4,7,0,5,7,0,5,3,1,4,6,8,7,0,5,2};
int[] result = sort(arr);
UtilTools.print(result);
}
static int[] sort(int[] arr){
int[] result=new int[arr.length];
int[] count=new int[10];
for (int i = 0; i < arr.length; i++) {
count[arr[i]]++;
}
//System.out.println(Arrays.toString(count));
/*//不稳定算法
for (int i = 0,j=0; i < count.length; i++) {
while (count[i]-- >0){
result[j++] =i;
}
}*/
//稳定算法
for (int i = 1; i < count.length; i++) {
count[i]=count[i]+count[i-1];
}
//System.out.println(Arrays.toString(count));
for (int i = arr.length - 1; i >= 0; i--) {
result[--count[arr[i]]] =arr[i];
}
return result;
}
}
【总结】
计数排序是非比较排序
适用于特定问题,也就是对源数据有要求
O,时间复杂度n+k,空间复杂度n+k
9 基数排序
非比较排序,桶思想的一种,多关键字排序。
【算法思想】
多关键字排序
package com.liweixiao;
import java.util.Arrays;
/**
* @author:LiWeixiao
* @date:2023/3/8
* @description:基数排序
*/
public class RadixSort {
public static void main(String[] args) {
int[] arr={421,240,115,532,305,430,124};
int[] result = sort(arr);
UtilTools.print(result);
}
static int[] sort(int[] arr){
int[] result=new int[arr.length];
int[] count=new int[10];
for (int i = 0; i < 3; i++) {
int division=(int)Math.pow(10,i);
for (int j = 0; j < arr.length; j++) {
int num=arr[j]/division % 10;
count[num]++;
}
//采用计数排序
for (int m = 1; m < count.length; m++) {
count[m]=count[m]+count[m-1];
}
for (int n = arr.length-1; n >= 0; n--) {
int num=arr[n]/division % 10;
result[--count[num]] =arr[n];
}
System.arraycopy(result,0,arr,0,arr.length);
Arrays.fill(count,0);
}
return result;
}
}
【总结】
1.本质上是一种多关键字排序
2.有低位优先和高位优先两种,LSD MSD(Least Significant Digit first),MSD属于分治的思想
3.百度百科的程序有问题
10 桶排序
【总结】
1.不太重要,理解就好
2.重点了解基数排序和计数排序
【应用场景】
(1)若n较小(如n≤50),可采用直接插入或直接选择排序。
当记录规模较小时,直接插入排序较好;否则因为直接选择移动的记录数少于直接插人,应选直接选择排序为宜。
(2)若文件初始状态基本有序(指正序),则应选用直接插人、冒泡或随机的快速排序为宜;
(3)若n较大,则应采用时间复杂度为O(nlgn)的排序方法:快速排序、堆排序或归并排序。
快速排序是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短;
堆排序所需的辅助空间少于快速排序,并且不会出现快速排序可能出现的最坏情况。这两种排序都是不稳定的。
若要求排序稳定,则可选用归并排序。但前面介绍的从单个记录起进行两两归并的排序算法并不值得提倡,通常可以将它和直接插入排序结合在一起使用。先利用直接插入排序求得较长的有序子序列,然后再两两归并之。因为直接插入排序是稳定 的,所以改进后的归并排序仍是稳定的。