数据结构学习笔记5-寻找最小的k个数(选择排序和堆排序)

来源:互联网 发布:淘宝秒杀不能付款 编辑:程序博客网 时间:2024/05/22 15:33

本文摘自《寻找最小的k个数》

题目描述

输入n个整数,输出其中最小的k个。

分析与解法

解法一

要求一个序列中最小的k个数,按照惯有的思维方式,则是先对这个序列从小到大排序,然后输出前面的最小的k个数。

至于选取什么的排序方法,我想你可能会第一时间想到快速排序(我们知道,快速排序平均所费时间为n*logn),然后再遍历序列中前k个元素输出即可。因此,总的时间复杂度:O(n * log n)+O(k)=O(n * log n)。

解法二

咱们再进一步想想,题目没有要求最小的k个数有序,也没要求最后n-k个数有序。既然如此,就没有必要对所有元素进行排序。这时,咱们想到了用选择或交换排序,即:

1、遍历n个数,把最先遍历到的k个数存入到大小为k的数组中,假设它们即是最小的k个数;
2、对这k个数,利用选择或交换排序找到这k个元素中的最大值kmax(找最大值需要遍历这k个数,时间复杂度为O(k));
3、继续遍历剩余n-k个数。假设每一次遍历到的新的元素的值为x,把x与kmax比较:如果x < kmax ,用x替换kmax,并回到第二步重新找出k个元素的数组中最大元素kmax‘;如果x >= kmax,则继续遍历不更新数组。

每次遍历,更新或不更新数组的所用的时间为O(k)或O(0)。故整趟下来,时间复杂度为n*O(k)=O(n*k)。

经典排序算法 - 选择排序Selection sort

顾名思意,就是直接从待排序数组里选择一个最小(或最大)的数字,每次都拿一个最小数字出来,

顺序放入新数组,直到全部拿完

再简单点,对着一群数组说,你们谁最小出列,站到最后边

然后继续对剩余的无序数组说,你们谁最小出列,站到最后边

再继续刚才的操作,一直到最后一个,继续站到最后边,现在数组有序了,从小到大

举例

先说看每步的状态变化,后边介绍细节,现有无序数组[6 2 4 1 5 9]

第一趟找到最小数1,放到最前边(与首位数字交换)

交换前:| 6 | 2 | 4 | 1 | 5 | 9 |

交换后:| 1 | 2 | 4 | 6 | 5 | 9 |

第二趟找到余下数字[2 4 6 5 9]里的最小数2,与当前数组的首位数字进行交换,实际没有交换,本来就在首位

交换前:| 1 | 2 | 4 | 6 | 5 | 9 |

交换后:| 1 | 2 | 4 | 6 | 5 | 9 |

第三趟继续找到剩余[4 6 5 9]数字里的最小数4,实际没有交换,4待首位置无须交换

第四趟从剩余的[6 5 9]里找到最小数5,与首位数字6交换位置

交换前:| 1 | 2 | 4 | 6 | 5 | 9 |

交换后:| 1 | 2 | 4 | 5 | 6 | 9 |

第五趟从剩余的[6 9]里找到最小数6,发现它待在正确的位置,没有交换

排序完毕输出正确结果[1 2 4 5 6 9]

第一趟找到最小数1的细节

当前数组是| 6 | 2 | 4 | 1 | 5 | 9 |

先把6取出来,让它扮演最小数

当前最小数6与其它数一一进行比较,发现更小数就交换角色

当前最小数6与2比较,发现更小数,交换角色,此时最小数是2,接下来2与剩余数字比较

当前最小数2与4比较,不动

当前最小数2与1比较,发现更小数,交换角色,此时最小数是1,接下来1与剩余数字比较

当前最小数1与5比较,不动

当前最小数1与9比较,不动,到达末尾

当前最小数1与当前首位数字进行位置交换,如下所示

交换前:| 6 | 2 | 4 | 1 | 5 | 9 |

交换后:| 1 | 2 | 4 | 6 | 5 | 9 |

完成一趟排序,其余步骤类似

我的代码如下:

#include<stdio.h>#include<string.h>#include<stdlib.h>void swap(int *l,int i,int j){    int temp=0;    temp=*(l+i);    *(l+i)=*(l+j);    *(l+j)=temp;}int selectSort(int *l,int length)//找出假设的l的最大值{    int i,j,max;//  for(i=0;i<length;i++)        {            i=0;            max=i;//假定第i个是最大值,赋给max,注意,是下标            for(j=i+1;j<length;j++)//j从i+1开始往后轮询                {                    if(*(l+max)<*(l+j))//如果找到更大的,则交换下标                        max=j;                }            //if(i!=max)//如果两者不等,说明需要此时的max下标对应的值才是真正的第i个大的值            //  swap(l,i,max);//交换两者的值        }    return max;}void findKSmallest(int *num,int numsize, int k){    int max=0;    if(num==NULL)//如果输入的num数组是空,则异常退出        exit(-1);    max=selectSort(num,k);//找出假设的p的最大值    for(int jj=k;jj<numsize;jj++)        {            if(*(num+jj)<*(num+max))//如果jj对应的值比p中最大值还大,那么需要交换                {                    swap(num,jj,max);                    max=selectSort(num,k);                }        }    for(int j=0;j<k;j++)        {            printf("the %d is %d\n",j,*(num+j));        }}int main(){    int num[]={1,3,5,78,9,6};    findKSmallest(num,sizeof(num)/sizeof(int),5);    return 0;}

解法三:利用堆排序:

更好的办法是维护容量为k的最大堆,原理跟解法二的方法相似:

1、用容量为k的最大堆存储最先遍历到的k个数,同样假设它们即是最小的k个数;
2、堆中元素是有序的,令k1

#include<stdio.h>void swap(int *l,int i,int j)//交换两数{    int temp=0;    temp=*(l+i);    *(l+i)=*(l+j);    *(l+j)=temp;}void MaxHeapify(int *a,int length,int i)//从i开始往后排列成最大堆{    int left=i*2;//左孩子下标,利用完全二叉树的性质    int right=i*2+1;//右孩子下标    int largest=0;//初始化最大的值的下标    if(left<length && a[left]>a[largest])        largest=left;    else        largest=i;    if(right<length && a[right]>a[largest])        largest=right;    if(largest!=i)        {            swap(a,i,largest);            MaxHeapify(a,length,largest);//递归调用        }}void BuildMaxHeap(int *a,int length)//建堆{    for(int i=length/2;i>=1;i--)        {            MaxHeapify(a,length,i);        }}int main(){    int k[]={0,1,3,5,6,7,9,1,100};    int length=sizeof(k)/sizeof(int);    int countnum=3;//要排列前countnum个最小的数    BuildMaxHeap(k,countnum);//先建一个大小为countnum=3的最大堆;则对顶元素k[1]为最大    for(int i=0;i<length-countnum;i++)        {            if(k[1]>k[i+countnum])//继续从countnum遍历k数组countnum后面的数,如果比堆顶元素还大,则更新堆                {                    k[1]=k[i+countnum];                    MaxHeapify(k,countnum,1);//注意,这里更新堆输入的长度依然是countnum                }        }    for(int jj=0;jj<countnum;jj++)//打印堆内的元素        printf("the %d num is %d\n",jj,k[jj]);    return 0;}

参考网址:
经典排序算法 - 选择排序Selection sort
程序员编程艺术:第三章、寻找最小的k个数

0 0
原创粉丝点击