剑指offer自学系列(一)

题目描述:输入n个整数,找出其中最小的k个数,例如,输入{4,5,1,6,2,7,3,8}这8个数字,最小的4个数字是1,2,3,4

题目分析:首先我能想到的是先对数组排序,从小到大,然后直接输出想要的最小的k个点,而根据排序算法,表现的比较好的快速排序时间复杂度为o(nlogn),但是有没有时间复杂度更小的呢?在快速排序中,我们写过一个partition函数,它的作用是将比基准值小的放到数组前面,大的放到数组后面,如果小的那部分长度不满足我们要求的k值,我们通过左移或者右移知道找到我们想要的k值,代码如下:

#include<iostream>
using namespace std;

int pa(int* input, int start, int end) {
    int begin = start;//快排函数将数组比一个值小的值放到数组前面,大的放后面
    int last = end;
    int key = input[begin];
    while (begin < last) {
        while (begin < last&&input[last] >= key) {
            last--;
        }
        input[begin] = input[last];
        while (begin < last&&input[begin] <= key) {
            begin++;
        }
        input[last] = input[begin];
    }
    input[begin] = key;
    return begin;
}

int *GetKLeastNum(int* input, int n, int k) {
    if (input == nullptr || k > n || n <= 0 || k <= 0) {
        return 0;
    }
    int start = 0;
    int end = n - 1;
    int index = pa(input, start, end);
    while (index != k - 1) {
        if (index > k - 1) {
            end = index - 1;
            index = pa(input, start, end);
        }
        else {
            start = index + 1;
            index = pa(input, start, end);
        }//如果快排基准值前面值数量小于k,那么在后面接着找基准值,否则往前面找基准值,直到找到前面有k个值的基准值
    }
    return input;
}

int main() {
    int k = 4;
    int n = 8;
    int input[8] = { 4,5,1,6,2,7,3,8 };
    int *value;
    value = GetKLeastNum(input, n, k);
    for (int i = 0; i < k; ++i) {
        cout << value[i] << endl;
    }
}

但是这样会改变数组位置,给原数组带来变化,若实际情况不允许变动,则需要重新选择新的方法:

创建一个可以装k个数的容器,输入值首先装满容器,当容器满了后,新输入的数与容器中的其他值比较,如果比最大值小,则最大值出容器,新来的值进容器,对这个容器,我们有两种想法,一个是最大堆,首先建立一个k个数的最大堆,每次拿一个数与堆顶元素比较,当这个数比堆顶元素大时,与堆顶元素交换,然后调整重新得到一个最大堆,遍历所有的数直到最小的k个值都进堆中。

n个数组成的堆高度为o(logn),和向量对应起来,有三条性质(根节点i(v)=0):

a)若节点v有左孩子,那么编号i(LeftChild(v))=2*i(v)+1

b)若节点v有右孩子,那么编号i(RightChild(v))=2*i(v)+2,

c)若节点v有父节点,那么编号i(Parent(v))=ceil[i(v)/2]-1,(ceil表示上取整)

而最大堆性质是任意节点值比它的子节点值都要大,最小堆是任意节点的值比它的子节点值都要小,通俗点理解就是最大堆从上到下任何一条路径都是从大到小排序好的,最小堆从上到下任何一条路径都是从小到大排序好的,找到前k个最小值代码如下:

#include<iostream>
using namespace std;

void swap(int* x1,int* x2) {
    int temp = *x1;
    *x1 = *x2;
    *x2 = temp;
}

void AdjustDown(int* input,int k, int parent) {
    if (input == NULL || k <= 0) { return; }
    int child = 2 * parent + 1;
    while (child<k) {
        if ((child + 1 < k) && (input[child] < input[child + 1]))
            ++child;//左节点和右节点选最大的值往上检索
        if (input[child] > input[parent]) {//从当前往上排序直到堆顶
            swap(&input[child], &input[parent]);
            parent = child;
            child = 2 * parent + 1;
        }
        else
            break;
    }
}

int *GetKLeastNum(int* input, int n ,int k) {
    int i = 0;
    for (i = (2 * k - 2) / 2; i >= 0;--i) {
        AdjustDown(input, k, i);//选择数组前k个值构建一个最大堆
    }
    for (i = k; i < n;++i) {
        if (input[i]<input[0]) {//将后n-k个值一个个送入最大堆,小于最大值则最大值弹出,重新构建最大堆
            swap(&input[i], &input[0]);
            AdjustDown(input, k, 0);
        }
    }    
    return input;
}

int main() {
    int k = 4;
    int n = 8;
    int input[8] = { 4,5,1,6,2,7,3,8 };    
    int *value;
    value = GetKLeastNum(input, n, k);
    for (int i = 0; i < k; ++i) {
        cout << value[i] << endl;
    }
}

另一种是红黑二叉树,红黑二叉树主要是对2-3查找树进行编码,需要满足以下条件:

(1)二叉树树根始终为黑色

(2)外部节点均为黑色

(3)如果节点为红色,则子节点均为黑色

(4)从任何一个外部节点到根节点的沿途,黑色节点数目相等

红黑树理解起来颇为复杂,等待后续更新

 

参考:

http://www.cnblogs.com/skywang12345/p/3610187.html(堆与二叉堆基础知识)

https://blog.csdn.net/pursue_my_life/article/details/80253469(堆的构建以及堆排序)

https://www.cnblogs.com/edisonchou/p/4799678.html(对应本题最大堆及测试方法)

posted @ 2019-03-18 16:57  薛定谔的哈士奇  阅读(165)  评论(0编辑  收藏  举报