一、直接插入排序
直接插入排序(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) | 稳定 |