《数据结构与算法分析》学习笔记四——散列表(哈希表)、优先队列(堆)

哈希表

          散列(hash)是以常数平均时间复杂度进行插入,删除和查找的继续,但不支持排序操作。

       每个关键字被映射到从0到TableSize-1范围中的某个数,并放到适当的单元中,这个映射就叫做散列函数(hash function),理想情况下不同关键字映射到不同单元,但由于单元有限,因此要寻找散列函数在单元间均匀地分配关键字。当两个关键字散列映射到同一个值的时候称为冲突。解决冲突的方法有几种,包括分离链接发和开放定址法。

        分离链接法将散列到同一个值的所有元素保留到一个表中,每个表都有表头,执行find时,使用散列函数确定究竟考察哪个表,然后遍历该表,找到并返回位置;执行insert时,遍历相应的表检查该元素是否已经处于适当的位置,若为新元素则插入表头或表尾。缺点是需要指针,分配地址减缓了速度,同时还需要实现链表。

         开放定址法也是用链表解决冲突,当发生冲突就尝试另外选择单元,直到找到空的单元。

           h(x) = (h(x) + F(i)) mod TableSize

         (1)线性探测法:函数F是i的线性函数,典型的为F(i)= i,若产生冲突,则放入下一个空闲地址。

          1,2,3,…,m-1

       故只要表足够大,总能找到一个自由单元,但花费时间相对多。更糟的是占据的单元会形成一些区块,称为一次聚集,散列到区块中任何关键字都需要多次试选才能添加到相应区块。

          (2)平方探测法:消除线性探测的一次聚集问题,平方探测的冲突函数为二次函数,如F = i^2。

          1^2,-1^2,2^2,-2^2,…,k^2,-k^2    ( k<=m/2 )

       缺点:一旦表被填满超过一半,但表的大小不是素数甚至在填满一半以前,就不能保证一次找到一个空单元了,因为最多有一半的表可以作为解决冲突的备选位置。

            (3)伪随机探测法:di=伪随机数序列

        再哈希法 :同时构造多个不同的哈希函数:Hi=RH1(key)  i=1,2,…,k,当哈希地址Hi=RH1(key)发生冲突时,再计算Hi=RH2(key)……,直到冲突不再产生。这种方法不易产生聚集,但增加了计算时间。

        建立公共溢出区:将哈希表分为基本表和溢出表两部分,凡是和基本表发生冲突的元素,一律填入溢出表。

 

优先队列(堆)

       堆是一棵完全二叉树,以最小堆为例,父亲结点的关键字一定小于其子结点,不需要指针,可以用数组存储。对数组任意位置i,其左儿子在2*i,右儿子在2*i+1,父亲在[i/2],唯一的问题在与最大的堆大小需要预先知道。

       插入:在下一个空间位置创建一个空穴,若插入该空穴不影响堆的序则直接插入,否则把空穴的父结点移入该空穴,空穴朝着根向上移一步,继续操作,直至新元素能够插入空穴为止。O(logN)

       删除:删除最小元,需将堆的最后一个结点X上移,根结点产生了一个空穴,若X可以插入,则直接插入,否则将空穴两个儿子的较小者移入空穴,重复操作,直至X可以插入空穴为止。O(logN)

priority_queue

  priority_queue 优先队列,其底层是用堆来实现的。在优先队列中,队首元素一定是当前队列中优先级最高的那一个。在优先队列中,没有 front() 函数与 back() 函数,而只能通过 top() 函数来访问队首元素(也可称为堆顶元素),也就是优先级最高的元素。

//下面两种优先队列的定义是等价的

    //下面两种优先队列的定义是等价的
    priority_queue<int> q;
    priority_queue<int,vector<int>,less<int> > q;//后面有一个空格

如果想让优先队列总是把最小的元素放在队首,只需进行如下的定义:

    priority_queue<int,vector<int>,greater<int> > q;

关键代码:

void heapadjust(vector<int> a, int start, int end) 
{ 
    int temp = a[start]; //当前根结点 
    while( 2 * start + 1 < end) 
    { 
        int i = 2 *start + 1;
        if (i + 1 < end && a[i + 1] > a[i])
            i ++;   
        if (a[i] < temp) 
            break; 
        a[start] = a[i];  //将孩子与根结点交换 
        start = i; 
    } 
    a[start] = temp;  
} 

K大数

1.直接进行排序,算法复杂度是O(N*logN)

   int findKthLargest(vector<int>& nums, int k) 
   {
        sort(nums.begin(), nums.end());
        return nums[nums.size() - k];
   }

2.用选择排序,冒泡法,或者交换排序这类的排序,对前K个元素进行排序。这三种算法也许不是最快的排序算法。但是都有个性质:计算出最大(小)的元素的算法复杂度是O(N)。这个过程不能中断,要计算第三大的元素必须建立在已经算出第二大的元素的基础上(因为每次都是计算当前数组最大)。所以它的算法复杂度是O(N*K);

int findKthLargest(vector<int>& nums, int k) 
{
        for(int i = 0; i < k; i ++)
        {
            int maxind = i;
            for(int j = i + 1; j < nums.size(); j ++)
            {
                if(nums[j] > nums[maxind])
                    max_ind = j;
            }
            if(maxind != i)
            {
                int temp = nums[maxind];
                nums[maxind] = nums[i];
                nums[i] = temp;
            }
        }
        return nums[k - 1];
    }

3.将数组内容存入一升序优先队列中,进行k-1次pop操作,那么队尾的元素就是第k大的数字, 实质上就是对数组建了个大顶堆,然后执行k – 1次删除栈顶元素后的栈顶;O(N + klogN)

int findKthLargest(vector<int>& nums, int k) 
{
    priority_queue<int> q;
    int len = nums.size();
    for(int val : nums){
            q.push(val);
    }
    while(q.size() > len-k+1)
        q.pop();
    return q.top();
}

4. k个数建堆,然后进行n-k次可能的插入操作,O(k+(N-k)logk) = O(Nlogk)

5,利用partition思想,见下一篇。

    原文作者:哈希算法
    原文地址: https://blog.csdn.net/qy724728631/article/details/81870840
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞