算法(一)---排序算法
(1)直接插入排序
基本介绍
直接插入排序是将一个待排序的记录,插入到前面已经排好序的有序序列中去,如此反复循环,直到全部排好顺序为止。
单个排序
//第一轮排序
public static void sort01(int arr[]){
// 先把下标为1的值作为待插入值
int insertVal=arr[1];//下标为1的值
//待插入值前面的值为被比较值
int insertIndex=1-1;//下标为1的值的前面,前面值下标为0
//给insertVal找到插入的位置
//要插入的值,从下标为0的数开始比,如果比前面的数小,说明待插入的数还没有找到位置
while (insertIndex >= 0 && insertVal <= arr[insertIndex]){
arr[insertIndex+1]=arr[insertIndex];//把值后移以为,让出空间让待插入值插入
insertIndex--;//insertIndex自减,就是接着往前面比较的意思
}
arr[insertIndex+1]=insertVal;//前面insertIndex已经减一了,这里加一,就等同于原来的arr[insertIndex]位置
}
//第二轮排序
public static void sort02(int arr[]){
int insertVal=arr[2];
int insertIndex=2-1;
while (insertIndex>=0 && insertVal<=arr[insertIndex]){
arr[insertIndex+1]=arr[insertIndex];
insertIndex--;
}
arr[insertIndex+1]=insertVal;
}
//第三轮排序
public static void sort03(int arr[]){
int insertVal=arr[3];
int insertIndex=3-1;
while (insertIndex>=0 && insertVal<=arr[insertIndex]){
arr[insertIndex+1]=arr[insertIndex];
insertIndex--;
}
arr[insertIndex+1]=insertVal;
}
整合排序
public static void sort(int arr[]){
for (int i = 1; i < arr.length; i++) {
// 先把下标为1的值作为待插入值
int insertVal=arr[i];//下标为1的值
//待插入值前面的值为被比较值
int insertIndex=i-1;//下标为1的值的前面,前面值下标为0
//给insertVal找到插入的位置
//要插入的值,从下标为0的数开始比,如果比前面的数小,说明待插入的数还没有找到位置
while (insertIndex >= 0 && insertVal <= arr[insertIndex]){
arr[insertIndex+1]=arr[insertIndex];//把值后移以为,让出空间让待插入值插入
insertIndex--;//insertIndex自减,就是接着往前面比较的意思
}
arr[insertIndex+1]=insertVal;//前面insertIndex已经减一了,这里加一,就等同于原来的arr[insertIndex]位置
}
}
进阶排序
public static void sort(int arr[]){
for (int i = 1; i < arr.length; i++) {
// 先把下标为1的值作为待插入值
int insertVal=arr[i];//下标为1的值
//待插入值前面的值为被比较值
int insertIndex=i-1;//下标为1的值的前面,前面值下标为0
//给insertVal找到插入的位置
//要插入的值,从下标为0的数开始比,如果比前面的数小,说明待插入的数还没有找到位置
while (insertIndex >= 0 && insertVal <= arr[insertIndex]){
arr[insertIndex+1]=arr[insertIndex];//把值后移以为,让出空间让待插入值插入
insertIndex--;//insertIndex自减,就是接着往前面比较的意思
}
//加一句判断,是否需要交换位置
if (insertIndex+1!=i){
arr[insertIndex+1]=insertVal;
}
arr[insertIndex+1]=insertVal;//前面insertIndex已经减一了,这里加一,就等同于原来的arr[insertIndex]位置
System.out.println("第"+i+"轮排序后的数组:"+ Arrays.toString(arr));
}
}
测试时间
80000个数据的数组,排序时间为2s
(2)希尔排序
基本介绍
在要排序的一组数中,根据某一增量分为若干子序列,并对子序列分别进行插入排序。然后逐渐将增量减小,并重复上述过程。直至增量为1,此时数据序列基本有序,最后进行插入排序。
单个排序
//希尔排序第一轮排序
public static void shellSort01(int arr[]){
int temp=0;
//把10个数分成5组
for (int i = 5; i < arr.length; i++) {
//遍历各组中所有的元素(共5组,每组有2个)步长5
for (int j = i-5; j >= 0; j-=5) {
//如果当前元素大于加上步长后的那个元素,说明交换
if (arr[j]>arr[j+5]){
temp=arr[j];
arr[j]=arr[j+5];
arr[j+5]=temp;
}
}
}
System.out.println("希尔排序第一轮的结果"+ Arrays.toString(arr));
}
//希尔排序第二轮排序
public static void shellSort02(int arr[]){
int temp=0;
//把10个数分成5组
for (int i = 2; i < arr.length; i++) {
//遍历各组中所有的元素(共5组,每组有2个)步长5
for (int j = i-2; j >= 0; j-=2) {
//如果当前元素大于加上步长后的那个元素,说明交换
if (arr[j]>arr[j+2]){
temp=arr[j];
arr[j]=arr[j+2];
arr[j+2]=temp;
}
}
}
System.out.println("希尔排序第二轮的结果"+ Arrays.toString(arr));
}
//希尔排序第三轮排序
public static void shellSort03(int arr[]){
int temp=0;
//把10个数分成5组
for (int i = 1; i < arr.length; i++) {
//遍历各组中所有的元素(共5组,每组有2个)步长5
for (int j = i-1; j >= 0; j-=1) {
//如果当前元素大于加上步长后的那个元素,说明交换
if (arr[j]>arr[j+1]){
temp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
}
}
}
System.out.println("希尔排序第三轮的结果"+ Arrays.toString(arr));
}
整合排序
//希尔排序交换法,效率较低,不如使用位移法速度快
public static void shellSort(int arr[]){
int temp=0;
//gap为步长,就是两个数之间的距离
for(int gap=arr.length/2; gap>0; gap/=2){
for (int i = gap; i < arr.length; i++) {
//遍历各组中所有的元素(共5组,每组有2个)步长5
for (int j = i-gap; j >= 0; j-=gap) {
//如果当前元素大于加上步长后的那个元素,说明交换
if (arr[j]>arr[j+gap]){
temp=arr[j];
arr[j]=arr[j+gap];
arr[j+gap]=temp;
}
}
}
// System.out.println("希尔排序一轮的结果"+ Arrays.toString(arr));
}
}
测试时间
80000个数据的数组,排序耗时10s
(3)冒泡排序
基本介绍
冒泡排序是对相邻的元素进行两两比较,较大的数下沉,较小的数上浮,最终达到有序
单个排序
//第一趟排序
public static void sort01(int arr[]){
int temp=0;//临时变量,用来交换位置
for (int i = 0; i < arr.length-1; i++) {
if (arr[i]>arr[i+1]){
temp=arr[i+1];
arr[i+1]=arr[i];
arr[i]=temp;
}
}
}
//第二趟排序
public static void sort02(int arr[]){
int temp=0;//临时变量,用来交换位置
for (int i = 0; i < arr.length-2; i++) {
if (arr[i]>arr[i+1]){
temp=arr[i+1];
arr[i+1]=arr[i];
arr[i]=temp;
}
}
}
//第三趟排序
public static void sort03(int arr[]){
int temp=0;//临时变量,用来交换位置
for (int i = 0; i < arr.length-3; i++) {
if (arr[i]>arr[i+1]){
temp=arr[i+1];
arr[i+1]=arr[i];
arr[i]=temp;
}
}
}
//第四趟排序
//可以看出来,数组长度为5,用了4次排序
public static void sort04(int arr[]){
int temp=0;//临时变量,用来交换位置
for (int i = 0; i < arr.length-4; i++) {
if (arr[i]>arr[i+1]){
temp=arr[i+1];
arr[i+1]=arr[i];
arr[i]=temp;
}
}
}
整合排序
public static void sort(int arr[]){
int temp=0;//临时变量,用来交换位置
for (int j = 1; j < arr.length-1; j++) {
for (int i = 0; i < arr.length-j; i++) {
if (arr[i]>arr[i+1]){
temp=arr[i+1];
arr[i+1]=arr[i];
arr[i]=temp;
}
}
}
}
进阶排序
public static void sortUpadte(int arr[]){
int temp=0;//临时变量,用来交换位置
boolean flag=false;//标识变量,表示是否进行过交换
for (int j = 1; j < arr.length-1; j++) {
for (int i = 0; i < arr.length-j; i++) {
if (arr[i]>arr[i+1]){
flag=true;
temp=arr[i+1];
arr[i+1]=arr[i];
arr[i]=temp;
}
}
System.out.println("第"+j+"趟排序后的数组:"+ Arrays.toString(arr));
if (!flag){
break;//在一趟排序中,不存在前面数比后面大,一次交换都没有发生过,就直接结束
}else {
flag=false;//重置flag,进行下一次判断
}
}
}
测试时间
80000个数的数组,排序时间为14s
(4)快速排序
基本介绍
先从数列中取出一个数作为key值;将比这个数小的数全部放在它的左边,大于或等于它的数全部放在它的右边;对左右两个小数列重复第二步,直至各区间只有1个数。
代码示例
public static void sort(int arr[], int left, int right){
int temp=0;
int l=left;//左下标
int r=right;//右下标
//中间轴
int pivot=arr[(left+right)/2];
//while循环的目的是让比pivot值小的放到左边,值大的放到右边
while (l<r){
//在pivot的左边一直找,找到大于等于pivot的值,才退出
while (arr[l]<pivot){
l+=1;
}
//在pivot的右边一直找,找到小于等于pivot的值,才退出
while (arr[r]>pivot){
r-=1;
}
//如果l>=r说明pivot的左右两边值,已经按照左边全部小于pivot,右边全部大于pivot
if (l>=r){
break;
}
//交换左右位置
temp=arr[l];
arr[l]=arr[r];
arr[r]=temp;
//如果交换之后,发现这个arr[l]==pivot。等于r--,前移
if (arr[l]==pivot){
r-=1;
}
//如果交换之后,发现这个arr[r]==pivot。等于l++,后移
if (arr[r]==pivot){
l+=1;
}
}
//如果l==r,必须l++,r--,否则出现栈溢出
if(l==r){
l+=1;
r-=1;
}
//向左递归
if (left<r){
sort(arr,left,r);
}
//向右递归
if (right>l){
sort(arr,l,right);
}
}
时间测试
快速排序8万数据,消耗时间1s不到
快速排序800万数据,消耗时间1s(希尔排序移动法需要3秒)——超级快,变态快
(5)简单选择排序
基本介绍
简单选择排序是每一趟从待排序的数据元素中选择最小(或最大)的一个元素作为首元素,直到所有元素排完为止,属于不稳定排序。
代码示例
public static void selectSort(int[] arr){
//选择排序是嵌套for循环,所以时间复杂度为O(n^2)
for (int i = 0; i < arr.length-1; i++) {
//第一轮排序
//先假设第一个为最小值
int minIndex=i;
int min=arr[i];
//循环比较一下,找最小值,循环结束后,min值就是数组中真正的最小值了
for (int j = i+1; j < arr.length; j++) {
//如果要求由大到小排序,只需要把条件改成"min<arr[j]"
if (min>arr[j]){
//说明当前最小值并不是最小值,还有更小的,所以把更小的赋值给min
min=arr[j];
minIndex=j;
}
}
//把最小值放在最左端,交换位置
if (minIndex!=i){
arr[minIndex]=arr[i];
arr[i]=min;
}
// System.out.println("第"+(i+1)+"轮排序后的结果:"+Arrays.toString(arr));
}
}
时间测试
80000个数据的数组,排序耗时8s
(6)堆排序
基本介绍
堆排序(Heapsort)是利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
代码示例
public class HeapSort {
public static void main(String[] args) {
//要求把数组进行升序排序,根据这个要求,确定使用的是大顶堆
int arr[]={4,6,8,5,9,90,89,56,-999};
heapSort(arr);
System.out.println("数组="+ Arrays.toString(arr));
int arr01[]=new int[8000000];
for (int i = 0; i < 8000000; i++) {
arr01[i]= (int) (Math.random()*8000000);
}
Date date1=new Date();
SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String date1Str=simpleDateFormat.format(date1);
System.out.println("排序前的时间是="+date1Str);
heapSort(arr01);
Date date2=new Date();
String date2Str=simpleDateFormat.format(date2);
System.out.println("排序后的时间是="+date2Str);
//8千万数据排序,耗时37秒
// 排序前的时间是=2019-11-07 21:23:44
// 排序后的时间是=2019-11-07 21:24:21
//8百万数据排序,耗时3秒
// 排序前的时间是=2019-11-07 21:26:01
// 排序后的时间是=2019-11-07 21:26:04
}
//编写一个堆排序的方法,参数是一个数组,给它一个数组,就这个方法就能求出结果
public static void heapSort(int arr[]){
int temp=0;
System.out.println("堆排序");
//最重要的一部分就是把一个“无序二叉树”不断的调整成“大顶堆”
//测试分布完成
/*adjustHeap(arr,1,arr.length);
System.out.println("第一次:"+ Arrays.toString(arr));//第一次:[4, 9, 8, 5, 6]
adjustHeap(arr,0,arr.length);
System.out.println("第二次:"+ Arrays.toString(arr));//第二次:[9, 6, 8, 5, 4]*/
//把上面的分步完成完善,完成最终的代码
//根据非叶子节点的计算公式,得到第一个非叶子节点,然后i递减,最后变成0,到根节点
for (int i = arr.length/2-1; i >= 0; i--) {
adjustHeap(arr,i,arr.length);
}
for (int j=arr.length-1; j>0;j--){
//交换位置
temp=arr[j];
arr[j]=arr[0];
arr[0]=temp;
adjustHeap(arr,0,j);
}
// System.out.println("数组="+ Arrays.toString(arr));
}
//把一个给定的数组(二叉树),调整成我们想要的大顶堆
//arr就是待调整的数组,i代表非叶子节点在数组中的索引,length就是还有多少个元素需要进行调整,length在逐渐减少
//这个方法用来:把i指向的非叶子节点的树,调整成一个大顶堆
public static void adjustHeap(int arr[], int i, int length){
//首先,取出来当前元素的值,保存在临时变量
int temp=arr[i];
//开始调整
for (int k = i*2+1; k <length ; k=k*2+1) {
//k = i*2+1,k就是i的左子节点,k=k*2+1,就是借着找k的左子节点,并且这些节点都在数组的length内
//1-如果左子节点<右子节点
if(k+1<length && arr[k]<arr[k+1]){
//就让k指向右子节点,因为调整时,i要和自己最大的子节点进行位置交换
k++;
}
//2-如果i非叶子节点比它的子节点小,那就要调整位置了,利用临时变量来交换位置
if(temp<arr[k]){
arr[i]=arr[k];//把较大的值赋给当前节点
i=k;//当前节点为i,现在变成k,也就是把非叶子节点的索引i往子节点身上移了
} else {
//如果父节点大于两个子节点,那就算了,不需要调整
break;
}
}
//当for循环结束以后,我们已经把i为父节点的树的最大值,放在了最顶上(局部,不是全部)
arr[i]=temp;//把temp的值放到最后调整后的位置
}
}
测试结果
8百万数据排序,耗时3秒
8千万数据排序,耗时37秒
(7)归并排序
基本介绍
归并排序是建立在归并操作上的一种有效的排序算法,采用分治法。首先考虑下如何将2个有序数列合并。这个非常简单,只要从比较2个数列的第一个数,谁小就先取谁,取了后就在对应数列中删除这个数。然后再进行比较,如果有数列为空,那直接将另一个数列的数据依次取出即可。
合并方法
/**合并的方法
* @MethodName: merge
* @param arr 排序的原始数组
* @param left 左边有序序列的初始索引
* @param right 右边索引
* @param mid 中间索引
* @param temp 做中转功能的数组
*
* @Author: AllenSun
* @Date: 2019/10/24 20:00
*/
public static void merge(int arr[], int left, int mid, int right, int temp[]){
System.out.println("运行了一次");//8个数合并了7次,10个数合并了9次
int i=left;//初始化i,左边有序序列的初始索引
int j=mid+1;//初始化j,右边有序序列的初始索引
int t=0;//指向temp数组的当前索引(中转表也需要索引,不然怎么知道插入的位置)
//(第一步)
//先把左右两边(有序)的数据按照规则填充到temp数组
//直到左右两边的有序序列,有一边处理完毕为止
while (i<=mid && j<=right){
//如果左边的有序序列的当前元素,小于等于右边有序序列的当前元素
//即左边的当前元素,填充到temp数组
//然后t++,i++
if (arr[i]<=arr[j]){
temp[t]=arr[i];
t+=1;
i+=1;
}else {//反之,把右边有序序列的当前元素,填充到temp数组
temp[t]=arr[j];
t+=1;
j+=1;
}
}
//(第二步)
//把有剩余数据的一边的数据一次全部填充到temp
while (i<=mid){//左边的有序序列还有剩余的元素,全部填充到temp
temp[t]=arr[i];
t+=1;
i+=1;
}
while (j<=right){
temp[t]=arr[j];
t+=1;
j+=1;
}
//(第三步)
//把temp数组的元素拷贝到arr
//注意,并不是每次都拷贝所有
t=0;
int tempLeft=left;
//打印出来每次索引的位置
System.out.println("tempLeft="+tempLeft+" right="+right);
while (tempLeft<=right){
arr[tempLeft]=temp[t];
t+=1;
tempLeft+=1;
}
}
分解+合并的方法
/**分解+合并的方法
* @MethodName: mergeSort
* @Author: AllenSun
* @Date: 2019/10/24 20:41
*/
public static void mergeSort(int arr[], int left, int right, int temp[]){
if (left<right){
int mid=(left+right)/2;//中间索引
//向左递归进行分解
mergeSort(arr, left, mid, temp);
//向右递归进行分解
mergeSort(arr, mid+1, right, temp);
//到合并
merge(arr, left, mid, right, temp);
}
}
时间测试
8万数据,耗时1秒
800万,耗时4秒
(8)基数排序
基本介绍
基数排序又叫“桶子法”,是桶排序的扩展,是将整数按位数切割成不同的数字,然后按每个位数分别比较。首先创建数组A[MaxValue];然后将每个数放到相应的位置上(如8放在下标8的数组位置);最后遍历数组,即为排序后的结果。
单个排序
public static void sort01(int[] arr){
//第一轮排序
//定义一个二维数组,表示10个桶,每个桶就是一个一维数组
//10是因为0-9有十位数,arr.length是因为一个桶最多能把所有数都放进去
int[][] bucket=new int[10][arr.length];
//定义一个一维数组来记录每个桶每次放入的数据个数
int bucketElementCounts[]=new int[10];
for (int j = 0; j < arr.length; j++) {
int digitOfElement=0;
//找到每个数个位为多少
digitOfElement = arr[j] % 10;
bucket[digitOfElement][bucketElementCounts[digitOfElement]]=arr[j];
bucketElementCounts[digitOfElement]++;
}
//按照这个桶的顺序(一维数组的下标一次取出数据,放入原来数组)
int index=0;
//遍历每一个桶,并且把桶里的数据放入原来的数组
for (int k=0; k<bucketElementCounts.length; k++){
if (bucketElementCounts[k] !=0){//如果这个桶里数据数不为零,那就遍历这个桶
//循环这个桶,就是第k个桶(就是第k个一维数组),放入
for (int l = 0; l < bucketElementCounts[k]; l++) {
//取出元素放到arr
arr[index++]=bucket[k][l];
}
}
//第一轮处理后,把每个bucketElementCounts[k]=0!!!
bucketElementCounts[k]=0;
}
}
public static void sort02(int[] arr){
//第一轮排序
//定义一个二维数组,表示10个桶,每个桶就是一个一维数组
//10是因为0-9有十位数,arr.length是因为一个桶最多能把所有数都放进去
int[][] bucket=new int[10][arr.length];
//定义一个一维数组来记录每个桶每次放入的数据个数
int bucketElementCounts[]=new int[10];
for (int j = 0; j < arr.length; j++) {
int digitOfElement=0;
//找到每个数个位为多少
digitOfElement = arr[j] /10 % 10;
bucket[digitOfElement][bucketElementCounts[digitOfElement]]=arr[j];
bucketElementCounts[digitOfElement]++;
}
//按照这个桶的顺序(一维数组的下标一次取出数据,放入原来数组)
int index=0;
//遍历每一个桶,并且把桶里的数据放入原来的数组
for (int k=0; k<bucketElementCounts.length; k++){
if (bucketElementCounts[k] !=0){//如果这个桶里数据数不为零,那就遍历这个桶
//循环这个桶,就是第k个桶(就是第k个一维数组),放入
for (int l = 0; l < bucketElementCounts[k]; l++) {
//取出元素放到arr
arr[index++]=bucket[k][l];
}
}
bucketElementCounts[k]=0;
}
}
public static void sort03(int[] arr){
//第一轮排序
//定义一个二维数组,表示10个桶,每个桶就是一个一维数组
//10是因为0-9有十位数,arr.length是因为一个桶最多能把所有数都放进去
int[][] bucket=new int[10][arr.length];
//定义一个一维数组来记录每个桶每次放入的数据个数
int bucketElementCounts[]=new int[10];
for (int j = 0; j < arr.length; j++) {
int digitOfElement=0;
//找到每个数个位为多少
digitOfElement = arr[j] /10 /10 % 10;
bucket[digitOfElement][bucketElementCounts[digitOfElement]]=arr[j];
bucketElementCounts[digitOfElement]++;
}
//按照这个桶的顺序(一维数组的下标一次取出数据,放入原来数组)
int index=0;
//遍历每一个桶,并且把桶里的数据放入原来的数组
for (int k=0; k<bucketElementCounts.length; k++){
if (bucketElementCounts[k] !=0){//如果这个桶里数据数不为零,那就遍历这个桶
//循环这个桶,就是第k个桶(就是第k个一维数组),放入
for (int l = 0; l < bucketElementCounts[k]; l++) {
//取出元素放到arr
arr[index++]=bucket[k][l];
}
}
bucketElementCounts[k]=0;
}
}
整合排序
public static void sort01(int[] arr){
//1-得到数组中最大的数的位数
int max=arr[0];//假设第一位数就是最大数
for (int i = 0; i < arr.length; i++) {
if (arr[i]>max){
max=arr[i];
}
}
//得到最大数是几位数
int maxLength=(max+"").length();
//第一轮排序
//定义一个二维数组,表示10个桶,每个桶就是一个一维数组
//10是因为0-9有十位数,arr.length是因为一个桶最多能把所有数都放进去
int[][] bucket=new int[10][arr.length];
//定义一个一维数组来记录每个桶每次放入的数据个数
int bucketElementCounts[]=new int[10];
//这里我们使用循环把代码处理一下
for (int i = 0, n = 1; i < maxLength; i++, n *= 10) {
//针对每个元素的对应位进行排序处理,第一次是个位,第二次是十位,第三次是百位
for (int j = 0; j < arr.length; j++) {
int digitOfElement=0;
//取出每个元素的对应位的值
digitOfElement = arr[j] / n % 10;
//放入到对应的痛中
bucket[digitOfElement][bucketElementCounts[digitOfElement]]=arr[j];
bucketElementCounts[digitOfElement]++;
}
//按照这个桶的顺序(一维数组的下标一次取出数据,放入原来数组)
int index=0;
//遍历每一个桶,并且把桶里的数据放入原来的数组
for (int k=0; k<bucketElementCounts.length; k++){
if (bucketElementCounts[k] !=0){//如果这个桶里数据数不为零,那就遍历这个桶
//循环这个桶,就是第k个桶(就是第k个一维数组),放入
for (int l = 0; l < bucketElementCounts[k]; l++) {
//取出元素放到arr
arr[index++]=bucket[k][l];
}
}
//第一轮处理后,把每个bucketElementCounts[k]=0!!!
bucketElementCounts[k]=0;
}
System.out.println("第"+(i+1)+"轮,排序结果为:"+Arrays.toString(arr));
}
}
时间测试
8万数据,耗时不到1秒
800万数据,耗时不到1秒
8000万数据,耗时还是不到1秒
8亿数据溢出了,典型的“用空间换时间”