目录
一、简介
常见的排序算法可以分为两大类:比较类排序,和非比较类排序。
- 比较类排序:通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),因此也称为非线性时间比较类排序。
- 非比较类排序:不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界,以线性时间运行,因此也称为线性时间非比较类排序。主要思路是通过将数值以哈希(hash)或分桶(bucket)的形式直接映射到存储空间来实现的。
算法复杂度总览:
二、选择排序
1、简介
选择排序是一种简单直观的排序算法。
它的工作原理:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后追加到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
如下图:
2、代码实现
package com.kgf.algorithm.sort;
/***
* 选择排序
* 它的工作原理:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后追加到已排序序列的末尾。
* 以此类推,直到所有元素均排序完毕。
*/
public class SelectSort {
public static void main(String[] args) {
int[] nums = {24, 69, 80, 57, 13};
SelectSort ss = new SelectSort();
ss.selectSort(nums);
for (int num : nums) {
System.out.print(num+"\t");
}
System.out.println();
}
/***
* 选择排序
* @param nums
*/
public void selectSort(int[] nums){
for (int i = 0; i < nums.length; i++) {
for (int j = i+1; j < nums.length; j++) {
if (nums[i]>nums[j]){
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
}
}
}
三、冒泡排序(Bubble Sort)
1、简介
冒泡排序也是一种简单的排序算法。
它的基本原理是:重复地扫描要排序的数列,一次比较两个元素,如果它们的大小顺序错误,就把它们交换过来。这样,一次扫描结束,我们可以确保最大(小)的值被移动到序列末尾。
这个算法的名字由来,就是因为越小的元素会经由交换,慢慢“浮”到数列的顶端。
如下图:
2、代码实现
package com.kgf.algorithm.sort;
/***
* 冒泡排序
* 冒泡排序也是一种简单的排序算法。
* 它的基本原理是:重复地扫描要排序的数列,一次比较两个元素,如果它们的大小顺序错误,就把它们交换过来。这样,一次扫描结束,我们可以确保最大(小)的值被移动到序列末尾。
* 这个算法的名字由来,就是因为越小的元素会经由交换,慢慢“浮”到数列的顶端
*/
public class BubbleSort {
public static void main(String[] args) {
int[] nums = {24, 69, 80, 57, 13};
BubbleSort bs = new BubbleSort();
bs.bubbleSort(nums);
for (int num : nums) {
System.out.print(num+"\t");
}
System.out.println();
}
public void bubbleSort(int[] nums){
for (int i = 0; i < nums.length; i++) {
for (int j = 1; j < nums.length-i; j++) {
if (nums[j]<nums[j-1]){
int temp = nums[j];
nums[j] = nums[j-1];
nums[j-1] = temp;
}
}
}
}
}
四、插入排序(Insertion Sort)
1、简介
插入排序的算法,同样描述了一种简单直观的排序。
它的工作原理是:构建一个有序序列。对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
2、算法分析
2.1、思想
插入排序(Insertion Sorting)的基本思想是:把n个待排序的元素看成为一个有序表和一个无序表,开始时有序表中只包含一个元素,无序表中包含有n-1个元素,排序过程中每次从无序表中取出第一个元素,在有序表中从后往前进行比较,将它插入到有序表中的适当位置,使之成为新的有序表。
2.2、实例
将101,34,119,1进行从小→大的排序
1)第一趟:此时有序表为101,无序表为:34,119,1
2)从后往前遍历有序表,将34和101进行比较,34<101,此时将101后移一个位置。此时已经遍历完有序表中的所有元素,故将34插入在101的前面,即有序表的第一个位置,得到新的有序表:34,101
3)第二趟:从后往前遍历有序表,将119与34和101进行比较,发现119均大于两者。故将119直接插在101后面,即有序表的最后一个位置,得到新的有序表:34,101,119
4)第三趟:从后往前扫描有序表,1<119,故将119往后移一个位置;1<101,将101往后移一个位置;1<34,将34往后移一个位置。此时已经遍历完有序表中的所有元素,故将1插入在34的前面,即有序表的第一个位置。
5)此时所有元素已经完全有序:1,34,101,119
3、代码实现
package com.kgf.algorithm.sort;
/***
* 插入排序
* 构建一个有序序列。对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入
*/
public class InsertionSort {
public static void main(String[] args) {
int[] nums = {24, 69, 80, 57, 13};
InsertionSort is = new InsertionSort();
is.insertionSort(nums);
for (int num : nums) {
System.out.print(num+"\t");
}
System.out.println();
}
private void insertionSort(int[] nums) {
for (int i = 1; i < nums.length; i++) {
int temp = nums[i];
int j = i-1;
for (; j >=0; j--) {
if (temp<nums[j]){
nums[j+1] = nums[j];
}else{
nums[j+1] = temp;
break;
}
}
nums[j+1] = temp;
}
}
}
五、希尔排序(Shell Sort)
1、简介
1959年由Shell发明,是第一个突破O(n2)的排序算法,是简单插入排序的改进版。
它与插入排序的不同之处在于,它会优先比较距离较远的元素。希尔排序又叫缩小增量排序。
希尔排序在数组中采用跳跃式分组的策略,通过某个增量将数组元素划分为若干组,然后分组进行插入排序,随后逐步缩小增量,继续按组进行插入排序操作,直至增量为1。
希尔排序中对于增量序列的选择十分重要,直接影响到希尔排序的性能。一些经过优化的增量序列如Hibbard经过复杂证明可使得最坏时间复杂度为O(n^3/2)。
2、分组的思想
上图中gap为5,说明要分成5组。
这5组分别用了五种颜色的线条连接起来了。
第1组:9、4
第2组:1、8
第3组:2、6
第4组:5、3
第5组:7、5
为什么要采取上面的分组方法呢?换一种方法可以吗?
例如:挨着的元素分为一组。
如果是上面的这种分组方式的话,排序之后会变成下面的情况。
如果是最开始的分组方法的话
如果是按照最开始的分组思想分组的话,最后会排序成
可以发现左边都是叫小的数据,右边都是较大的数据。
更方便把分成的每一个组进行插入排序。
3、缩小增量的过程
前面gap为5的情况排序后会变成下面情况
按照gap把数据分成了两组。
当数据很大的时候,数据的间隔很大。
小的数据会往前方,大的数据会往后放。
gap会逐渐缩小,间隔也会逐渐缩小。
整体的数据会更加趋于有序,这个时候使用直接插入排序效率会更高。
gap为2的时候会排序成下面情况
此时分为了1组,再排序一次后变成下面情况
此时的数据即为有序的。
4、代码实现
package com.kgf.algorithm.sort;
/***
* 希尔排序
*/
public class ShellSort {
public static void main(String[] args) {
int[] nums = {9,1,2,5,7,4,8,6,3,5};
ShellSort ss = new ShellSort();
ss.shellSort(nums);
for (int num : nums) {
System.out.print(num+"\t");
}
System.out.println();
}
/***
*
* @param nums
*/
public void shellSort(int[] nums){
int gap = nums.length/2;//设置起始的gap为数组长度的二分之一
while (gap>=1){//只要间隔大于1就一直循环
for (int i = 0; i < nums.length; i++) {//从0开始进行循环
int temp = nums[i];//记录当前的数据
int j = i+gap;//步长为gap
for (; j < nums.length; j+=gap) {
if (temp>nums[j]){
nums[j-gap] = nums[j];//相邻步长的数据进行交换
}else{
break;
}
}
nums[j-gap] = temp;//这里最后需要将之前记录的数据,插入到j-gap这个下标上,因为上面的循环在这个位置截止
}
gap = gap/2;
}
}
}
六、归并排序(Merge Sort)
1、简介
归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。
将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。
归并排序的时间复杂度是O(nlogn)。代价是需要额外的内存空间
归并排序(Merge sort)是建立在归并操作上的一种有效的排序算法,归并排序对序列的元素进行逐层折半分组,然后从最小分组开始比较排序,合并成一个大的分组,逐层进行,最终所有的元素都是有序的。
2、算法原理
这是一个无序数列:4、5、8、1、7、2、6、3,我们要将它按从小到大排序。按照归并排序的思想,我们要把序列逐层进行拆分
序列逐层拆分如下
然后从下往上逐层合并,首先对第一层序列1(只包含元素4)和序列2(只包含元素5)进行合并
创建一个大序列,序列长度为两个小序列长度之和,p1、p2指针分别指向两个小序列的第一个元素,p指向大序列的第一个元素
比较p1、p2指向的元素,4小于5,将4填入p指向的元素,p、p1往右移一位
此时,序列1已经没有元素,将序列2的元素依次填入大序列中
序列8和1,序列7和2,序列6和3,用同样的方式填入新的序列
接着,以4、5为序列1,1、8为序列2,继续进行合并
创建一个序列长度为4的大序列,p1指向序列1的第一个元素4,p2指向序列2的第一个元素1,p指向大序列的第一个元素
4和1比较,4大于1,1填入p指向的元素,p、p2往右移一位
4和8比较,4小于8,4填入p指向的元素,p、p1往右移一位
5和8比较,5小于8,5填入p指向的元素,p、p1往右移一位
自此,序列1已经没有元素,将序列2的元素依次填入大序列中
序列2、7和序列3、6以同样的方式合并成新的序列
最后,将序列1、4、5、8和序列2、3、6、7以同样的方式继续合并成新的序列
至此所有的元素都是有序的
3、代码实现
package com.kgf.algorithm.sort;
import java.util.Arrays;
/***
* 归并排序
*/
public class MergeSort {
public static void main(String[] args) {
Integer[] nums = {9,1,2,5,7,4,8,6,3,5};
MergeSort ss = new MergeSort();
ss.sort(nums,0,nums.length-1);
for (int num : nums) {
System.out.print(num+"\t");
}
System.out.println();
}
/***
* 使用归并排序
* @param nums
*/
private void sort(Integer[] nums,int startIndex,int endIndex) {
//判断下标
if (startIndex>=endIndex)return;
//折半查找
int midIndex = (startIndex+endIndex)/2;
//进行递归排序
sort(nums,startIndex,midIndex);
sort(nums,midIndex+1,endIndex);
//进行合并
mergeSort(nums,startIndex,endIndex,midIndex);
}
/**
* 进行子数据合并
* @param nums
* @param startIndex
* @param endIndex
* @param midIndex
*/
private void mergeSort(Integer[] nums, int startIndex, int endIndex, int midIndex) {
//创建一个临时数组
int[] tempArray = new int[(endIndex-startIndex)+1];
int index = 0;
int p1 = startIndex;
int p2 = midIndex+1;
while (p1<=midIndex && p2<=endIndex){
if (nums[p1]>nums[p2]){
tempArray[index++] = nums[p2++];
}else{
tempArray[index++] = nums[p1++];
}
}
while (p1<=midIndex){
tempArray[index++] = nums[p1++];
}
while (p2<=endIndex){
tempArray[index++] = nums[p2++];
}
for (int i = 0; i < tempArray.length; i++) {
nums[i + startIndex] = tempArray[i];
}
// System.out.println("nums="+ Arrays.deepToString(nums) +",startIndex="+startIndex+", endIndex="+endIndex+", midIndex="+midIndex);
}
}