您的位置:首页 > 其它

十大经典排序算法

2020-03-01 02:20 99 查看

一、直接插入排序
二、希尔排序
三、冒泡排序
四、快速排序
五、简单选择排序
六、堆排序
七、归并排序
八、基数排序
九、计数排序
十、桶排序

一、直接插入排序

【算法思想】

【代码】

public void insertSort(int[] nums){
int len = nums.length;      //len=10

for(int i=1;i<len;i++){     //每次排第i个元素。

int tmp = nums[i];      //第i趟排序就是把nums[i]插进来。

int j=i;
while(j>0 && nums[j-1]>tmp){
nums[j] = nums[j-1];
j--;
}
nums[j] = tmp;
}
}

时间复杂度:最好:o(n) 最坏:o(n2) 平均:o(n2)
稳定排序。

二、希尔排序

【算法思想】

我们选择增量gap=len/2,缩小增量继续以gap = gap/2的方式,这种增量选择我们可以用一个序列来表示,{n/2,(n/2)/2…1},称为增量序列。希尔排序的增量序列的选择与证明是个数学难题,我们选择的这个增量序列是比较常用的,也是希尔建议的增量,称为希尔增量,但其实这个增量序列不是最优的。


【代码】

public void shellSort(int[] nums){
int len = nums.length;

for(int gap = len/2; gap>=1; gap /= 2){  //增量gap

for(int i=gap; i<len ;i++){          //i++而不是i+=gap。
int tmp = nums[i];

int j=i;
while(j>=gap && nums[j-gap]>tmp){
nums[j] = nums[j-gap];
j -= gap;
}
nums[j] = tmp;
}
}
}

希尔排序的时间复杂度依赖于增量序列的函数,涉及到数学上尚未解决的难题。当n在某个特定范围时,希尔排序的时间复杂度约为o(n1.3)。最坏情况下为o(n2)。

三、冒泡排序

【算法思想】


【代码】

public void bubbleSort(int[] nums){
int len = nums.length;            //len=10

for(int i=0;i<len-1;i++){         //只需要9趟排序。i范围:0~8
for(int j=0;j<len-i-1;j++){   //第4趟排序,i=3,已经有3个元素排好。只需排len-3个元素:0~6。j范围:0~5
if(nums[j] > nums[j+1]){
int tmp = nums[j];nums[j] = nums[j+1];nums[j+1] = tmp;  //交换
}
}
}
}

改进:

public void bubbleSort(int[] nums){
int len = nums.length;            //len=10
boolean flag;

for(int i=0;i<len-1;i++){         //只需要9趟排序。i范围:0~8
flag = false;

for(int j=0;j<len-i-1;j++){   //第4趟排序,i=3,已经有3个元素排好。只需排len-3个元素:0~6。j范围:0~5
if(nums[j] > nums[j+1]){
int tmp = nums[j];nums[j] = nums[j+1];nums[j+1] = tmp;  //交换
flag = true;          //本趟有交换
}
}

if(flag == false) return;     //若本趟没有修改,则排序完成
}
}

时间复杂度:最好:o(n) 最坏:o(n2) 平均:o(n2)

四、快速排序

【思想】

【代码】

public void quickSort(int[] nums, int low, int high){
if(low >= high) return ;

int pos = partition(nums, low, high);
quickSort(nums, low, pos-1);
quickSort(nums, pos+1, high);
}

public int partition(int[] nums, int low, int high){
int pivot = nums[low];
while(low < high){
while(low < high && nums[high] >= pivot){  //找到右边第一个比它小的
high--;
}
nums[low] = nums[high];

while(low < high && nums[low] <= pivot){  //找到左边第一个比它大的
low++;
}
nums[high] = nums[low];
}

nums[low] = pivot;
return low;
}

时间复杂度:最好:o(nlogn) 最坏:o(n^2) 平均:o(nlogn)。
空间复杂度:平均:o(logn)
不稳定排序。

五、简单选择排序

【思想】

【代码】

public void selectSort(int[] nums){
int len = nums.length;           //len=8

for(int i=0;i<len-1;i++){        //每一趟排好一个元素。共需要7趟。
int min = i;

for(int j=i+1;j<len;j++){    //找到后面最小的元素
if(nums[j]<nums[min]){
min = j;
}
}

if(min != i){
int tmp=nums[i];nums[i]=nums[min];nums[min]=tmp;
}
}
}

时间复杂度:最好: o(n^2) 最坏:o(n^2) 平均: o(n^2) 。
空间复杂度:o(1)
不稳定排序。

六、堆排序

【思想】



【代码】

public void heapSort(int[] nums){
int len = nums.length;

buildMaxHeap(nums);           //建初堆

for(int i=len-1; i>0; i--){
int tmp=nums[0];          //交换
nums[0]=nums[i];
nums[i]=tmp;

adjustDown(nums, 0, i);  //筛第一个
}
}

public void buildMaxHeap(int[] nums){
int len = nums.length;

for(int i=(len-1)/2; i>=0; i--){
adjustDown(nums, i, len);
}
}

public void adjustDown(int[] nums, int k, int len){    //筛nums[k]
int tmp = nums[k];      //暂存nums[k]

for(int i=2*k+1; i<len ;i = 2*i+1){    //让i顺着左子树遍历
if(i+1<len && nums[i]<nums[i+1]){  //用i记录左右孩子中较大的一个。
i = i+1;
}
if(nums[i] <= tmp){      //左右孩子都比tmp小,则找到了tmp的最终存放位置。
break;
}else{
nums[k] = nums[i];   //把左右孩子中的较大值放到k这个位置。
k = i;               //现在i这个地方空出来了。k始终记录空着的位置。
}
}
nums[k] = tmp;
}

建堆:o(n);n-1次向下调整,每次调整时间为o(h)。
故最好、最坏、平均情况下: 时间复杂度为o(nlogn) 。
空间复杂度:o(1)
不稳定排序。

七、归并排序

【思想】

【代码】

public static void mergeSort(int[] nums, int low, int high){
if(low >= high) return;

int mid = (low+high)/2;
mergeSort(nums, low, mid);
mergeSort(nums, mid+1, high);
merge(nums, low , mid, high);

}
public static void merge(int[] nums, int low, int mid, int high){
int[] tmp = new int[high-low+1];  //暂时排放到tmp中

int i=low;
int j=mid+1;
int k=0;
while(i<=mid && j<=high){
if(nums[i]<=nums[j]){
tmp[k++] = nums[i++];
}else{
tmp[k++] = nums[j++];
}
}
while(i<=mid){
tmp[k++] = nums[i++];
}
while(j<=high){
tmp[k++] = nums[j++];
}

for(i=low,k=0;i<=high;i++){  //从tmp中放回原数组
nums[i]=tmp[k++];
}
}

每一趟归并的时间是o(n),共进行logn趟。所以时间复杂度为o(nlogn)。
空间复杂度为o(n)
稳定排序。

八、基数排序

【思想】

【代码】

public void radixSort(int[] nums){
//找出最大的数
int max = nums[0];
for(int num : nums){
if(num > max){
max = num;
}
}

//看看最大的数有几位
int n=0;            //记录做大的位数
while(max!=0){
max /= 10;
n++;
}

//创建10个桶,每个桶是一个队列
List<Queue<Integer>> bucket = new LinkedList<Queue<Integer>>();
for(int key=0; key<10; key++){
bucket.add(new LinkedList<Integer>());
}

//从低位到高位进行分配和收集
for(int i=0;i<n;i++){
//分配
for(int num:nums){
int key = (num/(int)(Math.pow(10, i)))%10;  //得到num的第i位。
bucket.get(key).offer(num);                 //将num放入到对应的桶中。
}

//收集
int k=0;
for(int j=0;j<10;j++){
while(!bucket.get(j).isEmpty()){
nums[k++] = bucket.get(j).poll();
}
}
}
}

共进行位数趟分配和收集。每一趟分配的时间为o(n),每一趟收集的时间是o(n+桶数)。所以,时间复杂度:o(位数*(n+n+桶数)) = o(位数*(n+桶数))
空间复杂度:o(n+桶数)。
稳定排序。

注:复杂度这里很多地方分析的不一样。欢迎讨论。

九、计数排序

【思想】

比如:3 5 4 4 6 8 10 8 共8个元素。
最小值为3,最大值为10。需要建立的桶长度为8。分别存储3的个数,4的个数,5的个数,…,10的个数。
最后遍历一遍桶,最后得到排序结果。3的个数有几个就输出几次,4的个数有几个就输出几次,…,10的个数有几个就输出几次。

即:

  1. 扫描一下整个数组array,获取最小值 min 和最大值 max。
  2. 创建新的桶(就是一个数组),长度为 max - min + 1
  3. 桶中 index 的元素记录的值是数组中某元素出现的次数。即:第一个桶存放元素min的个数,第二个桶存放min+1的个数…
  4. 最后输出目标整数序列,即遍历桶,输出相应元素以及对应的个数

【代码】

public void countingSort(int[] nums){
//找到最大最小数,确认桶的范围
int max,min;
max = min = nums[0];
for(int num : nums){
if(num > max) max = num;
if(num < min) min = num;
}

//创建桶,并初试化为0
int[] bucket = new int[max-min+1];   //初始默认为0.

//利用桶,对数组元素进行计数
for(int num : nums){
bucket[num-min]++;
}

//输出
int j=0;
for(int i=0; i<bucket.length; i++){
while(bucket[i] != 0){
nums[j++] = min+i;
bucket[i]--;
}
}
}

时间复杂度:o((n+桶数)),主要花在输出上面。
空间复杂度:o(桶数)。
稳定排序。

注:复杂度这里很多地方分析的不一样。欢迎讨论。

十、桶排序

【思想】

【代码】

public void bucketSort(int[] nums, int bucketSize){  //bucketSize:每个桶的宽度
//找到nums数组中数的范围,以便确定桶的数量
int max,min;
max = min = nums[0];
for(int num: nums){
if(num > max) max = num;
if(num < min) min = num;
}

int n = (max-min)/bucketSize + 1;  //桶数量为n

//创建n个桶
List<LinkedList<Integer>> bucket = new LinkedList<LinkedList<Integer>>();
for(int i=0; i<n; i++){
bucket.add(new LinkedList<Integer>());
}

//把数组元素放到桶中
for(int num :nums){
bucket.get((num-min)/bucketSize).add(num);
}

//对每个桶进行排序
for(int i=0;i<n;i++){
if(bucket.get(i).size()>1){
insertSort(bucket.get(i));
}
}

//输出
int j=0;
for(int i=0;i<n;i++){
while(!bucket.get(i).isEmpty()){
nums[j++] = bucket.get(i).remove(0);
}
}
}
public void insertSort(LinkedList<Integer> list){
//链表插入排序
int len = list.size();
for(int i=1;i<len; i++){
int e = list.remove(i);

int j=0;
while(j<len-1 && list.get(j)<e){
j++;
}
list.add(j, e);
}
}

时间复杂度:最好:每个数分到一个桶中,不需要排序,时间主要花在输出上,输出时间为o(n+桶数);最差:所有数分到一个桶中,时间主要花在排序上,为:o(n^2),因为这里是插入排序。平均时间复杂度:不考虑排序,或者认为每个桶元素不多的情况下,时间主要花在输出上,为o(n+桶数)
空间复杂度:o(n+桶数)。
稳定排序。

注:复杂度这里很多地方分析的不一样。欢迎讨论。

  • 点赞
  • 收藏
  • 分享
  • 文章举报
思域传媒 发布了7 篇原创文章 · 获赞 0 · 访问量 315 私信 关注
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: