java中常见算法的分析与实现

一、直接插入排序

直接插入排序(Straight Insertion Sorting)的基本思想:在要排序的一组数中,假设前面(n-1) [n>=2] 个数已经是排好顺序的,现在要把第n个数插到前面的有序数中,使得这n个数也是排好顺序的。如此反复循环,直到全部排好顺序。

代码实现:

首先设定插入次数,即循环次数,for(int i=1;i<length;i++),1个数的那次不用插入。

从最后一个数开始向前循环,如果插入数小于当前数,则进行替换。

public class Main{
	
	public static void insertSort(int [] a) {//定义函数
		int len = a.length;
		for(int i = 1; i < len;i++) {//进行循环,直接从第二个开始,跟第一个先比较
			int temp = a[i];//将数组中比较的那个数进行标记
			for(int j = i-1;j>=0;j--) {//将标记的那个数与前面的进行逐一比较
				if(temp>a[j]) {	//如果标记那个数大于前面的,则进行替换
					a[j+1] = a[j];
					a[j] = temp;
				}else {
					break;
				}
			}
		}
	}
	public static void main(String args[]) {
		int a [] = {7,6,2,9,15,66,78};
		insertSort(a);//调用函数
		for(int i = 0; i < a.length;i++) {
			System.out.print(a[i]+" ");
		}
	}
	
}

二、希尔排序

针对插入排序的效率低下问题,有人提出了希尔排序。希尔排序也称递减增量算法,是一种不稳定的排序。

希尔排序逻辑分析

对于直接插入排序问题,数据量巨大时。

将数的个数设为n,取奇数k=n/2,将下标差值为k的数分为一组,构成有序序列。

再取k=k/2 ,将下标差值为k的书分为一组,构成有序序列。

重复第二步,直到k=1执行简单插入排序。

是插入排序的高效改进版本,插入排序是通过相邻之间的元素比对,并两两更换位置。而希尔排序可以形象的表示为,非相邻(跨度大)元素处理的插入排序。

举例子说明:80 93 60 12 42 30 68 85 10

一共9个元素,9除以2获取增量整数为4,根据这个答案将序列拆分,拆分序列如下:

80 93 60 12

42 30 68 85

10

分成了四组序列,(80,42,10)(93,30)(60,68)(12,85)

对每一组进行插入排序处理,简单插入排序,是认定第一个是已排好序的,后续第N个元素和前面N-1个元素比较。

这里对(80,42,10)序列分析下,三个之间的下标差值都为4, 即为之前计算得出的增量,只需通过比较序列,第0位80和第4位42比较,更换位置生成新序列(42,80,10)。继续类似处理,最后获得(10,42,80)

 

四组序列内部进行插入排序后,内容为:(10,42,80)(30,93)(60,68)(12,85)

实际数组序列顺序为:10 30 60 12 42 93 68 85 80

新增量4 / 2 = 2;

拆分如下:

10 30

60 12

42 93

68 85

80

排序后数组实际顺序为:10 12 42 30 60 85 68 93 80

新增量2 / 2 = 1

这一步实际上就是普通的插入排序了,对相邻的两个元素比较,由于前面的操作,生成的序列几乎符合规则的顺序,因此这步插入排序并不会处理特别多的数据。即时还存在很多未排序元素,这步会全部比较。

最后序列为:10 12 30 42 60 68 80 85 93

此时新增1 / 2 = 0 (只获取整数部分)

可以认定终结排序

 

总结来说,希尔排序依赖与插入排序的规则,新增加了一个增量的概念,目的是增大数据比较的下标间隔跨度,减少元素的移动次数,来提高排序效率。
 

public class Main {
	
	public static void sheelSort(int [] a){
        int len=a.length;//单独把数组长度拿出来,提高效率
        while(len!=0){
            len=len/2;
            for(int i=0;i<len;i++){//分组
                for(int j=i+len;j<a.length;j+=len){//元素从第二个开始
                    int k=j-len;//k为有序序列最后一位的位数
                    int temp=a[j];//要插入的元素
                    while(k>=0&&temp<a[k]){//从后往前遍历
                        a[k+len]=a[k];
                        k-=len;//向后移动len位
                    }
                    a[k+len]=temp;
                }
            }
        }
    }
	public static void main(String args[]) {
		int a [] = {7,6,2,9,15,66,78};
		sheelSort(a);
		for(int i = 0; i < a.length; i++) {
			System.out.print(a[i]+" ");
		}
	}

}

三、简单选择排序

代码实现:

首先确定循环次数,并且记住当前数字和当前位置。

将当前位置后面所有的数与当前数字进行对比,小数赋值给key,并记住小数的位置。

比对完成后,将最小的值与第一个数的值交换。

重复2、3步。

public class Main {
	public static void main(String args[]) {
		int [] a = {1,5,3,88,66,23,58};
		
		for(int i = 0; i < a.length;i++) {
			int temp = a[i];
			for(int j = i+1;j<a.length;j++) { //寻找最大的那个数
				if(temp<a[j]) {
					a[i] = a[j];
					a[j] = temp;
					temp = a[i];
				}
			}
		}
		
		for(int i = 0; i < a.length;i++) {
			System.out.print(a[i]+" ");
		}
	}

}

四、堆排序

对简单选择排序的优化。

将序列构建成大顶堆。

将根节点与最后一个节点交换,然后断开最后一个节点。

重复第一、二步,直到所有节点断开。

根据一个节点获取子节点的方式

左节点:2 * i + 1

右节点:2 * i + 2

平级上一个节点:i - 1

 

                               

代码实现

public  void heapSort(int[] a){
           int len=a.length;
           //循环建堆
           for(int i=0;i<len-1;i++){
               //建堆
               buildMaxHeap(a,len-1-i);
               //交换堆顶和最后一个元素
               swap(a,0,len-1-i);
           }
       }
        //交换方法
       private  void swap(int[] data, int i, int j) {
           int tmp=data[i];
           data[i]=data[j];
           data[j]=tmp;
       }
       //对data数组从0到lastIndex建大顶堆
       private void buildMaxHeap(int[] data, int lastIndex) {
           //从lastIndex处节点(最后一个节点)的父节点开始
           for(int i=(lastIndex-1)/2;i>=0;i--){
               //k保存正在判断的节点
               int k=i;
               //如果当前k节点的子节点存在
               while(k*2+1<=lastIndex){
                   //k节点的左子节点的索引
                   int biggerIndex=2*k+1;
                   //如果biggerIndex小于lastIndex,即biggerIndex+1代表的k节点的右子节点存在
                   if(biggerIndex<lastIndex){
                       //若果右子节点的值较大
                       if(data[biggerIndex]<data[biggerIndex+1]){
                           //biggerIndex总是记录较大子节点的索引
                           biggerIndex++;
                       }
                   }
                   //如果k节点的值小于其较大的子节点的值
                   if(data[k]<data[biggerIndex]){
                       //交换他们
                       swap(data,k,biggerIndex);
                       //将biggerIndex赋予k,开始while循环的下一次循环,重新保证k节点的值大于其左右子节点的值
                       k=biggerIndex;
                   }else{
                       break;
                   }
               }
           }
       }

五、冒泡排序

冒泡排序(Bubble Sort),是一种计算机科学领域的较简单的排序算法

它重复地走访过要排序的元素列,依次比较两个相邻的元素,如果顺序(如从大到小、首字母从Z到A)错误就把他们交换过来。走访元素的工作是重复地进行直到没有相邻元素需要交换,也就是说该元素列已经排序完成。

这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端(升序或降序排列),就如同碳酸饮料中二氧化碳的气泡最终会上浮到顶端一样,故名“冒泡排序”。

时间复杂度

冒泡排序最好时间复杂度为O(n)

冒泡排序的最坏时间复杂度为O(n^2)

所以平均复杂度为O(n^2)

算法稳定性

冒泡排序就是把小的元素往前调或者把大的元素往后调。比较是相邻的两个元素比较,交换也发生在这两个元素之间。所以,如果两个元素相等,是不会再交换的;如果两个相等的元素没有相邻,那么即使通过前面的两两交换把两个相邻起来,这时候也不会交换,所以相同元素的前后顺序并没有改变,所以冒泡排序是一种稳定排序的算法。

public void bubbleSort(int []a){
           int len=a.length;
           for(int i=0;i<len;i++){
               for(int j=0;j<len-i-1;j++){//注意第二重循环的条件
                   if(a[j]>a[j+1]){
                       int temp=a[j];
                       a[j]=a[j+1];
                       a[j+1]=temp;
                   }
               }
           }
        }

六、快速排序

快速排序(Quicksort)是对冒泡排序的一种改进。

快速排序由C. A. R. Hoare在1960年提出。它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

快速排序原理

设要排序的数组是A[0]……A[N-1],首先任意选取一个数据(通常选 快排图 用数组的第一个数)作为关键数据,然后将所有比它小的数都放到它左边,所有比它大的数都放到它右边,这个过程称为一趟快速排序。值得注意的是,快速排序不是一种稳定的排序算法,也就是说,多个相同的值的相对位置也许会在算法结束时产生变动。

 一趟快速排序的算法是:

1)设置两个变量i、j,排序开始的时候:i=0,j=N-1;  

2)以第一个数组元素作为关键数据,赋值给key,即key=A[0];  

3)从j开始向前搜索,即由后开始向前搜索(j--),找到第一个小于key的值A[j],将A[j]和A[i]的值交换;  

4)从i开始向后搜索,即由前开始向后搜索(i++),找到第一个大于key的A[i],将A[i]和A[j]的值交换;  

5)重复第3、4步,直到i=j; (3,4步中,没找到符合条件的值,即3中A[j]不小于key,4中A[i]不大于key的时候改变j、i的值,使得j=j-1,i=i+1,直至找到为止。找到符合条件的值,进行交换的时候i, j指针位置不变。另外,i==j这一过程一定正好是i+或j-完成的时候,此时令循环结束)。

public void quickSort(int[]a,int start,int end){
           if(start<end){
               int baseNum=a[start];//选基准值
               int midNum;//记录中间值
               int i=start;
               int j=end;
               do{
                   while((a[i]<baseNum)&&i<end){
                       i++;
                   }
                   while((a[j]>baseNum)&&j>start){
                       j--;
                   }
                   if(i<=j){
                       midNum=a[i];
                       a[i]=a[j];
                       a[j]=midNum;
                       i++;
                       j--;
                   }
               }while(i<=j);
                if(start<j){
                    quickSort(a,start,j);
                }
                if(end>i){
                    quickSort(a,i,end);
                }
           }
       }

七、归并排序

归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。归并排序是一种稳定的排序方法。

归并操作

归并操作(merge),也叫归并算法,指的是将两个顺序序列合并成一个顺序序列的方法。

如 设有数列{6,202,100,301,38,8,1}

初始状态:6,202,100,301,38,8,1

第一次归并后:{6,202},{100,301},{8,38},{1},比较次数:3;

第二次归并后:{6,100,202,301},{1,8,38},比较次数:4;

第三次归并后:{1,6,8,38,100,202,301},比较次数:4;

总的比较次数为:3+4+4=11;

逆序数为14;

算法描述

归并操作的工作原理如下:

第一步:申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列

第二步:设定两个指针,最初位置分别为两个已经排序序列的起始位置

第三步:比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置

重复步骤3直到某一指针超出序列尾

将另一序列剩下的所有元素直接复制到合并序列尾

public  void mergeSort(int[] a, int left, int right) {
           int t = 1;// 每组元素个数
           int size = right - left + 1;
           while (t < size) {
               int s = t;// 本次循环每组元素个数
               t = 2 * s;
               int i = left;
               while (i + (t - 1) < size) {
                   merge(a, i, i + (s - 1), i + (t - 1));
                   i += t;
               }
               if (i + (s - 1) < right)
                   merge(a, i, i + (s - 1), right);
           }
        }

        private static void merge(int[] data, int p, int q, int r) {
           int[] B = new int[data.length];
           int s = p;
           int t = q + 1;
           int k = p;
           while (s <= q && t <= r) {
               if (data[s] <= data[t]) {
                   B[k] = data[s];
                   s++;
               } else {
                   B[k] = data[t];
                   t++;
               }
               k++;
           }
           if (s == q + 1)
               B[k++] = data[t++];
           else
               B[k++] = data[s++];
           for (int i = p; i <= r; i++)
               data[i] = B[i];
        }

八、基数排序

基数排序(radix sort)属于“分配式排序”(distribution sort),又称“桶子法”(bucket sort)或bin sort,顾名思义,它是透过键值的部份资讯,将要排序的元素分配至某些“桶”中,藉以达到排序的作用,基数排序法是属于稳定性的排序,其时间复杂度为O (nlog(r)m),其中r为所采取的基数,而m为堆数,在某些时候,基数排序法的效率高于其它的稳定性排序法。

实现方法

最高位优先(Most Significant Digit first)法,简称MSD法:先按k1排序分组,同一组中记录,关键码k1相等,再对各组按k2排序分成子组,之后,对后面的关键码继续这样的排序分组,直到按最次位关键码kd对各子组排序后。再将各组连接起来,便得到一个有序序列。

最低位优先(Least Significant Digit first)法,简称LSD法:先从kd开始排序,再对kd-1进行排序,依次重复,直到对k1排序后便得到一个有序序列。

实现原理

基数排序的发明可以追溯到1887年赫尔曼·何乐礼在打孔卡片制表机(Tabulation Machine)上的贡献。它是这样实现的:将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。

基数排序的方式可以采用LSD(Least significant digital)或MSD(Most significant digital),LSD的排序方式由键值的最右边开始,而MSD则相反,由键值的最左边开始。

代码

public void baseSort(int[] a) {
               //首先确定排序的趟数;
               int max = a[0];
               for (int i = 1; i < a.length; i++) {
                   if (a[i] > max) {
                       max = a[i];
                   }
               }
               int time = 0;
               //判断位数;
               while (max > 0) {
                   max /= 10;
                   time++;
               }
               //建立10个队列;
               List<ArrayList<Integer>> queue = new ArrayList<ArrayList<Integer>>();
               for (int i = 0; i < 10; i++) {
                   ArrayList<Integer> queue1 = new ArrayList<Integer>();
                   queue.add(queue1);
               }
               //进行time次分配和收集;
               for (int i = 0; i < time; i++) {
                   //分配数组元素;
                   for (int j = 0; j < a.length; j++) {
                       //得到数字的第time+1位数;
                       int x = a[j] % (int) Math.pow(10, i + 1) / (int) Math.pow(10, i);
                       ArrayList<Integer> queue2 = queue.get(x);
                       queue2.add(a[j]);
                       queue.set(x, queue2);
                   }
                   int count = 0;//元素计数器;
                   //收集队列元素;
                   for (int k = 0; k < 10; k++) {
                       while (queue.get(k).size() > 0) {
                           ArrayList<Integer> queue3 = queue.get(k);
                           a[count] = queue3.get(0);
                           queue3.remove(0);
                           count++;
                       }
                   }
               }
        }

表格

排序方法最好时间平均时间最坏时间辅助空间稳定性
直接插入O(n)O(n^2)O(n^2)O(1)稳定
二分插入O(n)O(n^2)O(n^2)O(1)稳定
希尔 O() O(1)不稳定
冒泡O(n)O(n^2)O(n^2)O(1)稳定
快速O(nlgn)O(nlgn)O(n^2)O(lgn)不稳定
直接选择O(n^2)O(n^2)O(n^2)O(1)不稳定
O(nlgn)O(nlgn)O(nlgn) 不稳定
归并O(nlgn)O(nlgn)O(nlgn)O(n)稳定
基数O(d(rd+n))O(d(rd+n))O(d(rd+n))O(rd+n)稳定

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值