十大排序算法
时间、空间复杂度比较
排序算法 | 平均时间复杂度 | 最差时间复杂度 | 空间复杂度 | 数据对象稳定性 |
---|---|---|---|---|
冒泡排序 | O(n2) | O(n2) | O(1) | 稳定 |
选择排序 | O(n2) | O(n2) | O(1) | 数组不稳定、链表稳定 |
插入排序 | O(n2) | O(n2) | O(1) | 稳定 |
快速排序 | O(n*log2n) | O(n2) | O(log2n) | 不稳定 |
堆排序 | O(n*log2n) | O(n*log2n) | O(1) | 不稳定 |
归并排序 | O(n*log2n) | O(n*log2n) | O(n) | 稳定 |
希尔排序 | O(n*log2n) | O(n2) | O(1) | 不稳定 |
计数排序 | O(n+m) | O(n+m) | O(n+m) | 稳定 |
桶排序 | O(n) | O(n) | O(m) | 稳定 |
基数排序 | O(k*n) | O(n2) | 稳定 |
冒泡排序
算法思想
- 比较相邻的元素。如果第一个比第二个大,就交换他们两个
- 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数
- 针对所有的元素重复以上的步骤,除了最后一个
- 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较
void bubbleSort(int a[], int n)
{
for(int i=0;i<n-1;++i)
{
for(int j=0;j<n-i-1;s++j)
{
if(a[j] > a[j+1])
{
int tmp = a[j] ; //交换
a[j] = a[j+1] ;
a[j+1] = tmp;
}
}
}
}
选择排序
算法思想
- 在未排序序列中找到最小(大)元素,存放到排序序列的起始位置
- 从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾
- 以此类推,直到所有元素均排序完毕
public static void select(int[] a){
for(int i=0;i<a.length-1;i++){
int index = i;
for(int j=i+1;j<a.length-1;j++){
if(a[j] < a[index]){
index = j;
}
}
if(i != index){
int temp = a[i];
a[i] = a[index];
a[index] = temp;
}
}
}
插入排序
算法思想
- 从第一个元素开始,该元素可以认为已经被排序
- 取出下一个元素,在已经排序的元素序列中从后向前扫描
- 如果该元素(已排序)大于新元素,将该元素移到下一位置
- 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
- 将新元素插入到该位置后
- 重复步骤2~5
public static void insert(int[] a){
int i,j;
for(i=1;i<a.length;i++){
int temp = a[i];
for(j=i-1;j>=0;j--){
if(a[j] > temp) {
a[j + 1] = a[j];
}else{
break;
}
}
a[j+1] = temp;
}
}
快速排序
算法思想
- 选取第一个数为基准
- 将比基准小的数交换到前面,比基准大的数交换到后面
- 对左右区间重复第二步,直到各区间只有一个数
public static void quick(int[] a,int low,int high){
int i,j,t,temp;
if(low > high){
return;
}
i = low;
j = high;
temp = a[low];
while(i < j){
while(a[j] >= temp && i < j){
j--;
}
while(a[i] <= temp && i < j){
i++;
}
if(i < j){
t = a[j];
a[j] = a[i];
a[i] = t;
}
}
a[low] = a[i];
a[i] = temp;
quick(a,low,j-1);
quick(a,j+1,high);
}
堆排序
堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点
算法思想
- 构建初始堆,将待排序列构成一个大顶堆(或者小顶堆),升序大顶堆,降序小顶堆
- 将堆顶元素与堆尾元素交换,并断开(从待排序列中移除)堆尾元素
- 重新构建堆
- 重复2~3,直到待排序列中只剩下一个元素(堆顶元素)
public static void heapSort(int[] arr){
//创建堆
for(int i=(arr.length-1)/2;i>=0;i--){
//从第一个非叶子节点开始,从下至上调整左右结构
adjustHeap(arr, i ,arr.length);
}
//调整堆结构+交换堆顶元素与末尾与元素
for(int i=arr.length-1;i>0;i--) {
int temp = arr[i];
arr[i] = arr[0];
arr[0] = temp;
//重新对堆进行调整
adjustHeap(arr, 0,i);
}
}
public static void adjustHeap(int[] arr, int parent, int length){
//将temp作为父节点
int temp = arr[parent];
int lChild = 2*parent + 1;
while(lChild < length){
//右孩子
int rChild = lChild + 1;
//如果有右孩子节点,并且右孩子节点的值大于左孩子的值,则选取右孩子节点
if(rChild < length && arr[lChild] < arr[rChild]){
lChild++;
}
//如果节点的值已经大于孩子节点的值,则直接结束
if(temp >= arr[lChild]){
break;
}
//把孩子节点的值赋给父节点
arr[parent] = arr[lChild];
//选取孩子节点的左孩子节点,继续向下筛选
parent = lChild;
lChild = 2*lChild + 1;
}
arr[parent] = temp;
}
归并排序
归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并
算法思想
- 把长度为n的输入序列分成两个长度为n/2的子序列
- 对这两个子序列分别采用归并排序
- 将两个排序好的子序列合并成一个最终的排序序列
public static void merge(int[] a,int low,int mid,int high,int[] temp){
int i = 0;
int j = low ;int k = mid + 1;
while(j <= mid && k <= high){
if(a[j] < a[k]){
temp[i++] = a[j++];
}else{
temp[i++] = a[k++];
}
}
while(j <= mid){
temp[i++] = a[j++];
}
while(k <= high){
temp[i++] = a[k++];
}
for(int t=0;t<i;t++){
a[low+t] = temp[t];
}
}
public static void mergeSort(int[] a,int low,int high,int[] temp){
if(low < high){
mergeSort(a,low,(low + high) / 2,temp);
mergeSort(a,((low + high) / 2) + 1,high,temp);
merge(a,low,(low + high) / 2,high,temp);
}
}
希尔排序
希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本。但希尔排序是非稳定排序算法。先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序
算法思想
- 选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1
- 按增量序列个数k,对序列进行k 趟排序
- 每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度
public static void shell(int[] a){
int i,j;
int temp = 0;
int ic = 0;
for(ic=a.length/2;ic>0;ic=ic/2){
for(i=ic;i<a.length;i=i+ic){
temp = a[i];
for(j=i-ic;j>=0;j=j-ic){
if(temp < a[j]){
a[j+ic] = a[j];
}else{
break;
}
}
a[j+ic] = temp;
}
}
}
计数排序
计数排序统计小于等于该元素值的元素的个数i,于是该元素就放在目标数组的索引i位(i≥0)
- 计数排序基于一个假设,待排序数列的所有数均为整数,且出现在(0,k)的区间之内
- 如果 k(待排数组的最大值) 过大则会引起较大的空间复杂度,一般是用来排序 0 到 100 之间的数字的最好的算法,但是它不适合按字母顺序排序人名
- 计数排序不是比较排序,排序的速度快于任何比较排序算法
算法思想
- 找出待排序的数组中最大和最小的元素
- 统计数组中每个值为 i 的元素出现的次数,存入数组 C 的第 i 项
- 对所有的计数累加(从 C 中的第一个元素开始,每一项和前一项相加)
- 向填充目标数组:将每个元素 i 放在新数组的第 C[i] 项,每放一个元素就将 C[i] 减去 1
public static int[] countSort(int[] a, int k){
int[] C = new int[k+1];
int length = a.length,sum = 0;
int[] B = new int[length];
for(int i=0;i<length;i++){
C[a[i]] += 1;
}
for(int i=0;i<k+1;i++){
sum += C[i];
C[i] = sum;
}
for(int i=length-1;i>=0;i--){
B[C[a[i]]-1] = a[i];
C[a[i]]--;
}
return B;
}
桶排序
将值为i的元素放入i号桶,最后依次把桶里的元素倒出来
算法思想
- 设置一个定量的数组当作空桶子
- 寻访序列,并且把项目一个一个放到对应的桶子去
- 对每个不是空的桶子进行排序
- 从不是空的桶子里把项目再放回原来的序列中
public static void bucketSort(float[] arr) {
// 新建一个桶的集合
ArrayList<LinkedList<Float>> buckets = new ArrayList<LinkedList<Float>>();
for (int i = 0; i < 10; i++) {
// 新建一个桶,并将其添加到桶的集合中去。
// 由于桶内元素会频繁的插入,所以选择 LinkedList 作为桶的数据结构
buckets.add(new LinkedList<Float>());
}
// 将输入数据全部放入桶中并完成排序
for (float data : arr) {
int index = getBucketIndex(data);
insertSort(buckets.get(index), data);
}
// 将桶中元素全部取出来并放入 arr 中输出
int index = 0;
for (LinkedList<Float> bucket : buckets) {
for (Float data : bucket) {
arr[index++] = data;
}
}
}
/**
* 计算得到输入元素应该放到哪个桶内
*/
public static int getBucketIndex(float data) {
// 这里例子写的比较简单,仅使用浮点数的整数部分作为其桶的索引值
// 实际开发中需要根据场景具体设计
return (int) data;
}
/**
* 我们选择插入排序作为桶内元素排序的方法 每当有一个新元素到来时,我们都调用该方法将其插入到恰当的位置
*/
public static void insertSort(List<Float> bucket, float data) {
ListIterator<Float> it = bucket.listIterator();
boolean insertFlag = true;
while (it.hasNext()) {
if (data <= it.next()) {
it.previous(); // 把迭代器的位置偏移回上一个位置
it.add(data); // 把数据插入到迭代器的当前位置
insertFlag = false;
break;
}
}
if (insertFlag) {
bucket.add(data); // 否则把数据插入到链表末端
}
}
基数排序
一种多关键字的排序算法,可用桶排序实现
算法思想
- 取得数组中的最大数,并取得位数
- arr为原始数组,从最低位开始取每个位组成radix数组
- 对radix进行计数排序(利用计数排序适用于小范围数的特点)
public class RadixSort {
private static void radixSort(int[] array,int d)
{
int n=1;//代表位数对应的数:1,10,100...
int k=0;//保存每一位排序后的结果用于下一位的排序输入
int length=array.length;
int[][] bucket=new int[10][length];//排序桶用于保存每次排序后的结果,这一位上排序结果相同的数字放在同一个桶里
int[] order=new int[length];//用于保存每个桶里有多少个数字
while(n<d)
{
for(int num:array) //将数组array里的每个数字放在相应的桶里
{
int digit=(num/n)%10;
bucket[digit][order[digit]]=num;
order[digit]++;
}
for(int i=0;i<length;i++)//将前一个循环生成的桶里的数据覆盖到原数组中用于保存这一位的排序结果
{
if(order[i]!=0)//这个桶里有数据,从上到下遍历这个桶并将数据保存到原数组中
{
for(int j=0;j<order[i];j++)
{
array[k]=bucket[i][j];
k++;
}
}
order[i]=0;//将桶里计数器置0,用于下一次位排序
}
n*=10;
k=0;//将k置0,用于下一轮保存位排序结果
}
}