排序算法日常使用的最基本的算法之一,大体可以分为外部排序和内部排序。
排序算法 | 平均时间复杂度 | 最好情况 | 最坏情况 | 空间复杂度 | 稳定性 |
---|---|---|---|---|---|
冒泡排序 | O(n^2) | O(n) | O(n^2) | O(1) | 稳定 |
选择排序 | O(n^2) | O(n^2) | O(n^2) | O(1) | 不稳定 |
插入排序 | O(n^2) | O(n) | O(n^2) | O(1) | 稳定 |
希尔排序 | O(nlogn) | O(n) | O(n ^ s) 1O(1) |
不稳定 |
|
归并排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(n) | 稳定 |
快速排序 | O(nlogn) | O(nlogn) | O(n ^ 2) | O(nlogn) | 不稳定 |
堆排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(1) | 不稳定 |
计数排序 | O(n + k) | O(n + k) | O(n + k) | O(k) | 稳定 |
桶排序 | O(n + k) | O(n + k) | O(n^2) | O(n + k) | 稳定 |
基数排序 | O(n * k) | O(n * k) | O(n * k) | O(n + k) | 稳定 |
在基本有序情况下,高效地算法是:直接插入排序,冒泡排序
在无序情况下:高效的算法是:堆排序,快速排序,归并排序。
关于时间复杂度:
关于稳定性:
依次比较相邻的两个数,把小数放在前面,大数放在后面。首先第一趟,比较第一个和第二个数,小数在前,大数在后,然后比较第二和第三两个数,小数在前,大数在后,如此依次比较,直至到数组末尾第一趟结束。针对除了最后一个元素的所有元素重复以上步骤,直至没有任何一对数字需要比较。
规律:N个数字排序,总共进行N-1趟排序,每趟将一个最大值移至末尾。
示例:以3,1,0,2,4,9,8,6,7,5
为例:
结果 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
/**
* 冒泡排序
* */
public void bubbleSort(int[] nums) {
int n = nums.length;
for (int i = 0;i < n-1; i ++) {
boolean flag = true;
for (int j = 0;j < n - i-1; j++) {
if (nums[j] > nums[j + 1]) {
int tmp = nums[j];
nums[j] = nums[j + 1];
nums[j + 1] = tmp;
flag = false;
}
}
if (flag) break;
}
}
首先在待排序列中选择最小(大)元素,存放到待排序列的起始(结束)位置。然后从剩余待排序列中继续选择最小(大元素),存放到已排序列的末尾。重复以上步骤,直至整个序列排序完成。
示例:以3,1,0,2,4,9,8,6,7,5
为例:
结果 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
/**
* 简单选择排序
* */
public void selectionSort(int[] nums) {
for (int i = 0;i < nums.length; i++) {
int small_index = i;
for (int j = i + 1; j < nums.length; j++) {
if (nums[j] < nums[small_index]) {
small_index = j;
}
}
if (i < small_index) {
int tmp = nums[i];
nums[i] = nums[small_index];
nums[small_index] = tmp;
}
System.out.println("* 第" + (i+1) + "趟:" + Arrays.toString(nums));
}
}
把初始n个待排序的元素,看成有序表+无序表。初始状态,有序表只有待排序序列的第一个元素,无序表中有n-1个元素。每次排序,从无序表中取出第i个原则,插入都有序表中对应合适的位置。重复这个步骤,直至所有元素都插入有序表,代表整个序列排序完成。
示例:以3,1,0,2,4,9,8,6,7,5
为例:
结果:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
/**
* 直接插入排序
* */
public void insertSort(int[] nums) {
if(nums==null||nums.length==0)
return;
for (int i = 1;i < nums.length; i++) {
int tmp = nums[i] , j = i;
if (nums[j - 1] > tmp) {
while (j >= 1 && nums[j - 1] > tmp) {
nums[j] = nums[j - 1];
j--;
}
}
nums[j] = tmp;
System.out.println("* 第" + (i) + "趟:" + Arrays.toString(nums));
}
}
首先选择一个增量序列 t1 , t2, … , tk , 其中ti > tj , tk = 1; 然后按照增量分别将待排数组分成多个子序列,使每个子序列元素个数相对较少,然后对各个子序列分别进行直接插入排序,代整个排序序列基本有序后,对所有元组再进行一次直接插入排序。
示例:以3,1,0,2,4,9,8,6,7,5
为例:
/**
* 希尔排序
* */
public static void hillSort(int[] nums){
if(nums == null || nums.length <= 1){
return;
}
int i,j;
int increment;
int temp;
for(increment=nums.length/2;increment>0;increment/=2) {
for(i=increment;i<nums.length;i++) {
temp=nums[i];
for(j=i-increment;j>=0;j-=increment) {
if(temp<nums[j]) {
nums[j+increment]=nums[j];
}else
break;
}
nums[j+increment]=temp;
}
}
}
归并排序是利用归并的思想实现的排序方法,该方法采用的经典的分治策略(把问题分解成若干个小问题然后递归求解)。首先我们需要申请空间,使其大小为两个已排序序列之和,该空间用来存放合并后的序列。然后我们设定两个指针,最初位置分别是两个已经排序序列的起始位置。比较两个比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置;重复这个步骤 直到某一指针达到序列尾;然后将将另一序列剩下的所有元素直接复制到合并序列尾。
示例:以3,1,0,2,4,9,8,6,7,5
为例:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
/**
* 归并排序
* */
public static void mergeSort(int []arr){
int []temp = new int[arr.length];//在排序前,先建好一个长度等于原数组长度的临时数组,避免递归中频繁开辟空间
sort(arr,0,arr.length-1,temp);
}
private static void merge(int[] arr,int left,int mid,int right,int[] temp){
int i = left;//左序列指针
int j = mid+1;//右序列指针
int t = 0;//临时数组指针
while (i<=mid && j<=right){
if(arr[i]<=arr[j]){
temp[t++] = arr[i++];
}else {
temp[t++] = arr[j++];
}
}
while(i<=mid){//将左边剩余元素填充进temp中
temp[t++] = arr[i++];
}
while(j<=right){//将右序列剩余元素填充进temp中
temp[t++] = arr[j++];
}
t = 0;//因为需要拷贝所以要把临时数组的指针置0(即指向第一个元素)
//将temp中的元素全部拷贝到原数组中
while(left <= right){
arr[left++] = temp[t++];
}
}
private static void sort(int[] arr,int left,int right,int []temp){
if(left<right){
int mid = (left+right)/2;
sort(arr,left,mid,temp);//左边归并排序,使得左子序列有序
sort(arr,mid+1,right,temp);//右边归并排序,使得右子序列有序
merge(arr,left,mid,right,temp);//将两个有序子数组合并操作
}
}
每次挑选出一个元素为基准
,设两个指针,一个left
一个right
,分别指向序列的头和尾。用两个指针扫描数组,把所有比基准大的,都摆放在基准的后面,所有比基准小的,都摆放在基准的后面。这一次扫描之后,基准就位于它的最终位置。然后分别在基准左侧和基准右侧再次执行这个操作。知道整个序列排列完成。
示例:以3,1,0,2,4,9,8,6,7,5
为例:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
/**
* 快速排序
* */
private void quickSort(int nums[], int left , int right) {
if (left < right) {
int i = left , j = right,temp = nums[left];
System.out.println(" * "+Arrays.toString(nums));
while (i < j) {
while (nums[j] > temp && i < j) j--;
if (i < j){
nums[i] = nums[j]; i++;
}
while (nums[i] < temp && i < j) i++;
if (i < j) {
nums[j] = nums[i]; j--;
}
}
nums[i] = temp;
quickSort(nums , left , i -1);
quickSort(nums , i+1 , right);
}
}
首先,将待排序列构造成一个大顶堆。我们用数组+限定规则构造大顶堆,下标为i
的数组元素是下标为2*i+1
与2*i+2
元素的父节点。且i
的数组元素的父节点下标为(i-1)/2
此时堆顶元素就是序列的最大值。然后我们把最后一个位置的数和堆顶位置的数做交换,把最大值放到数组最后的位置。此时让堆大小减1,最大值就被固定在了末尾。
把除最后一个元素的序列重新调整成大顶堆,然后依次执行上面的步骤,输出倒数第二大的数。依次类推,把所有元素都输出之后,整个序列就变成了有序序列。
示例:以3,1,0,2,4,9,8,6,7,5
为例:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
/**
* 堆排序
* */
public void heapSort(int [] arr) {
if(arr==null||arr.length<2) {
return;
}
for(int i=0;i<arr.length;i++) {
heapInsert(arr,i);//依次将数组中i位置上的数加进来,让它0~i之间的数形成大顶堆
}
int heapSize=arr.length;//堆大小heapSize一开始等于数组的全部
swap(arr,0,--heapSize);//最后一个位置上的数与第一个位置上的数(堆顶元素)交换,堆大小减1,即最后一个位置上的数不动了
while(heapSize>0) {
heapify(arr,0,heapSize);//从0位置开始,将当前形成的堆继续调整为一个大顶堆
swap(arr,0,--heapSize);//最后一个位置上的数与第一个位置上的数(堆顶元素)交换,堆大小减1,即最后一个位置上的数不动了
}
}
//建立大顶堆的过程
public void heapInsert(int[] arr,int index) {
while(arr[index] > arr[(index-1)/2]) {//当前index位置上的数若比其父结点上的数大,则交换他俩的位置
swap(arr,index,(index-1)/2);
index=(index-1)/2;//然后index来到了它的父节点位置,继续上面的while
}
}
//若有一个节点的值变小了,则需要往下沉(与其左右孩子中较大的数进行交换位置的)的操作
public void heapify(int[] arr,int index,int heapSize) {
int left=index*2+1;//左孩子下标
while(left<heapSize) {//未越界,即该节点并非叶子节点,存在左孩子
//该节点有右孩子,让largest作为左右孩子较大值的下标
int largest=(left+1 < heapSize) && arr[left+1] > arr[left] ? left+1 : left;
//让largest成为该节点与该节点左右孩子中较大值的下标
largest=arr[largest] > arr[index] ? largest : index;
if(largest==index) {
break;
}
swap(arr,largest,index);//largest!=index
index=largest;//该节点下标变成了较大孩子的下标
left=index*2+1;//继续往下走,重复上面的while
}
}
public void swap(int[] arr,int i,int j) {
int temp=arr[i];
arr[i]=arr[j];
arr[j]=temp;
}
代码已上传至本人Github仓库 , 欢迎大家star,follow