数组里最小的k个元素&&快排

这个题目之前在看数据结构和算法分析的时候见过,其实解法也挺多的。

  • 排序后找出前k个元素
    快排O(NlogN),然后O(K),总的复杂度为O(NlogN)

  • 创建一个长度为k的容器,然后不断地维护更新,这样的话,每次都要:
    1,将当前的k个元素中的最大者与当前遍历的元素比较
    2,若当前的元素更小,则需要替换这个值
    3,若有上一步的话,将新的数组排序
    据说用二叉树的话复杂度为O(logK)。。
    然后遍历n-k的元素
    总的复杂度为O(Nlog(k))

  • 和快速排序一样,我们改造一下得到快速选择quickSelect
    首先大家如果不太熟悉快排,可以去看一下。
    这里我们要打印的元素只是前k个,因此我们把快排的算法改进一下

快排:

void quick_sort(int* a, int left, int right)
{
    int i, j;
    int pivot;

    if (left + 3<= right)
    {
        //pivot = Median3(a, left, right);
        pivot = get_pivot(a, left, right);//error1
        i = left;
        j = right - 1;
        for (;;)
        {
            while (a[++i] < pivot){}
            while (a[--j] > pivot){}
            if (i < j)
                swap(&a[i], &a[j]);
            else
                break;
        }
        swap(&a[i], &a[right - 1]);

        quick_sort(a, left, i - 1);
        quick_sort(a, i + 1, right);
    }
    else
        InsertionSort(a + left, right - left + 1);
}

特别要提到的是if (left + 3<= right)这里加上了一个cutoff量
我试了一下,如果这个值是0,1,2的话,会出错
大家可以宏定义一个cutoff,然后自己设置值,这一点呢,我猜是为了防止越界的时候出现问题,交给插入排序来做

其中pivot的程序:

//不仅是找到中位数,而且为了方便要把pivot移动到最右边(hide),这样就不用去处理这个元素了
//技巧:找中位数不要局限在比较大小,这样很麻烦的,可以通过swap快速达到效果
int get_pivot(int*arr, int beg, int end)
{
    
    int middle = (beg + end) / 2;                   //change2
    if (arr[middle] < arr[beg])
        swap(&arr[middle], &arr[beg]);
    if (arr[end] < arr[beg])
        swap(&arr[beg], &arr[end]);
    if (arr[middle]>arr[end])
        swap(&arr[middle], &arr[end]);
    //hide pivot
    swap(&arr[middle], &arr[end - 1]);//we don't need to deal with arr[end],but it's ok if you do

    return arr[end - 1];
}

swap:
void swap(int* a, int *b) { int tmp = *a; *a = *b; *b = tmp; }
快排里面,
quick_select(arr,beg,i-1); quick_select(arr,i+1,end);

这一句可以改进而使quickselect更快速,快排的复杂度为O(N*logN),而下面的算法则可以达到O(N)!

.

因为我们只需要保证前k个元素是最小的;而我们每次调用,都保证了pivot前面的元素不大于pivot && pivot后面的元素不小于pivot

我们有一个下标 i 来记录pivot的位置,所以我们可以判断k与i的关系来决定对那一部分继续进行递归:

1,如果k小于i,那么只需要对前面的i个进行递归调用
2,如果k等于i+1,说明刚好有k个元素,直接打印
3,如果k大于i,那么前i个元素就不需要管理,处理后面的一部分

如下:

        //quickSelect:
        //attention that Kth value in array is arr[k-1]
        if (k < i)
            quick_select(arr, beg, i - 1, k);
        else
            if (k>i + 1)
                quick_select(arr, i + 1, end, k);
        //如果k比中间要长,那么 i 之前的都不需要管了,只需要看后面的部分

        //所以我们看见,quickSelect只需要对一边进行递归,所以,复杂度会低于quicksort

以上是quickSort,接下来是select:
函数的本身并没有什么变化,只是改动了上面提到的两句递归部分:

/*if (k - 1 == i)          //it seems that it dosen't matter
        return;
else*/ 
  if (k < i)
    quick_select(arr, beg, i - 1, k);
else
  if (k>i + 1)
    quick_select(arr, i + 1, end, k);

*不过我试了一下,第一种情况k - 1 == i可以注释掉。。

void quick_select(int* arr, int beg, int end, int k)
{
    int pivot;
    int i, j;

    if (beg +2 <= end)//<= matters
    {
        pivot = get_pivot(arr, beg, end);
        i = beg;
        j = end - 1;//it is okay to be j=end
        while (1)
        {
            while (arr[++i] < pivot);//it doesn't matter wether it is "++i" or "i++"
            while (arr[--j] > pivot);
            if (i < j)
                swap(&arr[i], &arr[j]);
            else break;
        }
        swap(&arr[i], &arr[end - 1]);//since you hide, restore here

        //follow the quickSort :
        //quick_select(arr, beg, i - 1, k);
        //quick_select(arr, i + 1, end, k);

        //quickSelect:
        //attention that Kth value in array is arr[k-1]
        if (k - 1 == i)
            return;
            else if (k < i)
            quick_select(arr, beg, i - 1, k);
            else
            if (k>i + 1)
            quick_select(arr, i + 1, end, k);
        //如果k比中间要长,那么 i 之前的都不需要管了,只需要看后面的部分

        //所以我们看见,quickSelect只需要对一边进行递归,所以,复杂度会低于quicksort
    }
    else
        InsertionSort(arr + beg, end - beg + 1);
}

我用例子试了一下是没有问题的:

    int a[] = { 5, 3, 8, 99, 10, 2, 4, 7, 1, 22 };
    quick_select(a, 0, 9, 6);
    //Qsort(a, 0, 9);
    //quick_sort(a, 0, 9);
    for (int i = 0; i < 10; i++)
        std::cout << a[i] << " ";

补充

我在做这道题的时候在网上搜索过,还有一种题目是让你输出第k个元素。。在以上程序中稍稍改进,加入返回的值然后输出
像这样就可以了:

if (k - 1 == i)
        return arr[i];
        else if (k < i)
            return quick_select_k(arr, beg, i - 1, k);
        else
            if (k>i + 1)
                return quick_select_k(arr, i + 1, end, k);

  • 记录一下我遇到的坑:
    在写get_pivot()的时候,我最开始是这样写的:

    //int middle = arr[(beg + end) / 2];                    //change2
    //if (middle < arr[beg])
    //  swap(&middle, &arr[beg]);
    //if (arr[end] < arr[beg])
    //  swap(&arr[beg], &arr[end]);
    //if (middle>arr[end])
    //  swap(&middle, &arr[end]);
    //swap(&middle, &arr[end - 1]);
    //beg<middle<end

第一行就出错了啊!
int middle = arr[(beg + end) / 2];
我tm调试了一两个小时才找到这个问题啊!一直以为我算法错了啊!
这里在数组里面用除法,似乎是有问题的!


update:
上stackoverflow问了一下发现果然很sb啊!
我那种错误的写法根本没有达到交换的效果,只是把middle这个值赋给了数组里面的一个元素!

学习了!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,117评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,963评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,897评论 0 240
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,805评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,208评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,535评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,797评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,493评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,215评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,477评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,988评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,325评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,971评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,807评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,544评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,455评论 2 266

推荐阅读更多精彩内容

  • 背景 一年多以前我在知乎上答了有关LeetCode的问题, 分享了一些自己做题目的经验。 张土汪:刷leetcod...
    土汪阅读 12,658评论 0 33
  • 总结一下常见的排序算法。 排序分内排序和外排序。内排序:指在排序期间数据对象全部存放在内存的排序。外排序:指在排序...
    jiangliang阅读 1,268评论 0 1
  • quicksort可以说是应用最广泛的排序算法之一,它的基本思想是分治法,选择一个pivot(中轴点),将小于pi...
    黎景阳阅读 414评论 0 1
  • 一秒变对称。。。
    黑人不是牙膏阅读 236评论 0 0
  • 回家的季节,也是欠薪的季节。到家后,被欠薪也是一种谈资。他在村里雄赳赳地大谈在那个南方城市里,有多少身家过亿的老板...
    妄想先生阅读 291评论 0 1