《编程珠玑》第14、15章读书笔记

第三部分 应用

14

本章用堆结构解决了两个重要问题:

1. 排序,对n元数组排序,所需时间不超过O(nlogn),而且只需要额外几个字的额外空间。

2. 优先级队列,堆通过插入新元素和提取最小元素这两种操作来维护元素集合,每个操作所需的时间都为O(nlogn)

 

       堆的数据结构实现最常见的二叉树表示方法,需要记录和指针。

任何结点的值都小于或等于其孩子的值的完全二叉树为小根堆

任何结点的值都大于或等于其孩子的值的完全二叉树为大根堆

注意:堆使用的是从下标1开始的数组。

两个关键函数

siftup函数重新获得堆性质:通过交换该结点与其父结点,尽可能地将新元素向上筛选。运行时间和log n成正比。

void siftup(n)

    pre n >0 && heap(1,n-1)

    post heap(1,n)

  i=n

  loop

    if i==1

     break

    p=i/2

    if x[p] <= x[i]

     break

   swap(p,i)

    i=p

    

       siftdown向下筛选时,将顺序不对的元素和比它小的子结点交换。

void siftdown(n)

      pre heap(2,n) && n>=0

     post heap(1,n)

    i=1

    loop

     c=2*i

      if c>n

        break

      if c+1<=n

        if x[c+1]<x[c]

           c++

      if x[i]<=x[c]

        break

     swap(c,i)

      i=c

堆排序

堆有两个比较重要的操作:插入和删除。插入时,我们一般将新插入的元素放在末尾,这时需要做向上调整;删除时一般是删除节点元素,这时需要做向下调整,以确保堆的性质。

所谓优先队列是不同于先进先出的普通队列,而是每次从队列中取出具有最高优先权的元素。根据优先队列这一特点(每次取出最高优先权的元素),我们可以很自然地想到用堆来实现优先队列。

优先级队列提供了一种简单的向量排序算法,优先在优先级队列中依次插入每个元素,然后按序删除它们。

代码如下:

public:

    priQueue(int m) :maxsize(m)

    {

        x = new T[maxsize + 1];

        n = 0;

    }

 

    void insert(T t)

    {

        int i, p;

        x[++n] = t;

 

        //自底向上类似siftup函数内容实现优先级序列

        for (i = n ; i > 1 && x[p=i/2] > x[i] ;  i = p)

            swap(p, i);

    }

 

    //输出队列顶并调整队列结构

    T extramin()

    {

        /*cout << "队列中的数据为:" << endl;

        for (int i = 1; i <= n; i++)

        cout << x[i] << "\t";

        cout << endl;*/

 

        int i, c;

        T t = x[1];

        x[1] = x[n--];

 

        //自顶向下调整队列结构

        for (i = 1; (c = 2 * i) <= n; i = c)

        {

            if (c + 1 <= n && x[c + 1] < x[c])

                c++;

            if (x[i] <= x[c])

                break;

            swap(c, i);

        }

        return t;

    }

 

本章最终总结以下几个原理:

1.       高效性,堆数据结构形状保证了堆中所有节点和根节点之间相差的层数在logn内,由于树是平衡的,所以函数siftup和siftdown的运行效率很高。堆排序通过在同一个实现数组中包含两种抽象结构(堆和元素序列)来避免使用额外的开销;

2.       正确性;循环体执行始终保持不变式为真;形状和顺序性质是另一种不变式;

3.       抽象性,一个好的工程师能够分清某个组件做什么(用户看到的抽象功能)和如何做(黑盒实现)之间的差别;

4.       过程抽象;

5.       抽象数据类型:数据类型做什么是由它的方法和方法的规范给出的,而如何做则是由具体实现决定的;

 

 15字符串

       讨论了一些字符串的经典问题

1.    单词

【问题一】为文档中包含的单词生成一个列表,用标准模板库中的setstrings可以简单实现

int main(int argc, char **argv)

{

    set<string> str;

    set<string>::iterator iter;

    string t;

    while(cin >> t)

    {

        str.insert(t);

    }

    for(iter = str.begin(); iter != str.end(); iter++)

    {

        cout << *iter << endl;

    }

    return 0;

}

while循环读取输入并将每个单词插入集合str(忽略重复的单词),然后for循环迭代整个集合,并按排好的顺序输出单词。

【问题二】对文档中的每个单词出现的次数进行统计,使用模板中的map将整数计数与每个字符串联系起来。

int main(int argc, char **argv)

{

    map<string, int> m;

    map<string, int>::iterator iter;

    string t;

    while(cin >> t)

    {

        m[t]++;

    }

    for(iter = m.begin(); iter != m.end(); iter++)

    {

        cout << iter->first << " " << iter->second << endl;

    }

    return 0;

}

此外为减少处理时间,可以选择建立自己的散列表,结构和散列函数如下

typedef struct node *nodeptr;

typedef struct node

{

    char *word; //指向单词的指针

    int count;    //单词出现的频率

    nodeptr next; //指向下一个节点的指针

}node;

散列函数将每个字符串映射为一个小于NHASH的正整数

unsigned int hash(char *p)

{

    unsigned int h = 0;

    for( ; *p != ''; p++)

    {

        h = MULT * h + *p;

    }

    return h % NHASH;

}

 

int main(void){

for i=[0,NHASH)

bin[i]=NULL;

                 while scanf(“%s”,buf) != EOF;

 incword(buf);

  for i=[0,NHASH)

      for (p=-bin[i];p1=NULL;p=p->next)

print p->word,p->count;

  return 0;

}

主要操作函数是incword,增加原先存在的单词的计数值,若之前没有该单词,则对计数器初始化

void incword(char *s)

{

    nodeptr p;

    int h = hash(s);    //找到与单词对应的箱

    for(p = bin[h]; p != NULL; p = p->next)

    {

        if(strcmp(s, p->word) == 0)

        {

            (p->count)++;

            return;

        }

    }

    p = nmalloc();

    p->count = 1;

    p->word = smalloc(strlen(s) + 1);

    strcpy(p->word, s);

    p->next = bin[h];    //头插法

    bin[h] = p;

}

该散列表比C++标准模板库中的映射快一个数量级,但缺乏平衡树提供的最坏情况性能保证,,也不能支持其他设计顺序的操作。

2.    短语

【问题三】给定一个文本文件作为输入,查找在其中最长的重复子字符串。

直接方法:双重for循环依次比较每个字符串,找到最长重复子字符串。时间复杂度为n2

方法二:采用后缀数组来处理该问题
    使用一个称为后缀数组的数据结构,这个结构是一个字符指针数组,记为a。读取输入时,我们对a进行初始化,使得每个元素指向输入字符串中的相应字符:
    while(ch  = getchar() != EOF)
    {
            a[n] = &c[n];
            c[n] = ch;
    }
    c[n] = 0; //空字符是所有字符串结束的标志
    元素a[0]指向整个字符串,下一个元素指向从第二个字符开始的数组后缀,等等。对于输入字符串“banana”,该数组能够表示如下后缀:
                            char *a="banana"
                            a[0]=banana
                            a[1]=anana
                            a[2]=nana
                            a[3]=ana
                            a[4]=na
                            a[5]=a
    如果某个长字符串在数组c中出现了两次,那么它将出现在两个不同的后缀中,因此我们对数组进行排序以寻找相同的后缀。“banana”数组排序为:
                                      a[0]=a
                            a[1]=ana
                            a[2]=anana
                            a[3]=banana
                            a[4]=na
                            a[5]=nana
    然后就可以扫描数组,通过比较相邻元素来找出最长的重复字符串。
    该方法由于排序的存在,时间复杂度为O(nlogn)

3.    生成随机文本

基于字母:下一个字母设置为前一个字母的随机函数,或者下一个字母设置为前k个字母的随机函数。

基于单词:
    方法一:最笨的方法是随机输出字典中的单词;
    方法二:稍微好点的方法是读取一个文档,对每个单词进行计数,然后根据适当的概率选择下一个输出的单词;
    方法三:如果使用在生成下一个单词时考虑前面几个单词的马尔科夫链,可以得到更加令人感兴趣的文本;
    下面是香农的算法:以构建【字母级别的一阶文本】为例,随机打开一本书并在该页随机选择一个字母记录下来。然后翻到另一页开始读,直到遇到该字母,此时记录喜爱其后面的那个字母。再翻到另外一页搜索上述第二个字母并记录其后面的那个字母。依次类推。对于【字母级别的1阶、2阶文本和单词级别的0阶、1阶文本】,处理过程类似。

int wordncmp(char *p, char* q)

{   

   int n = k;

   for ( ; *p == *q; p++, q++)

   {

        if (*p == 0 && --n == 0)

            return 0;

   }

   return *p - *q;

}

该函数在字符相同时持续扫描两个字符串。每当遇到空字符时,计数器n1,并找到K个相同的单词后返回相同;当遇到不同的字符时,返回差别。

int sortcmp(char **p, char **q)

{   

   return wordncmp(*p, *q);

}

 

char *skip(char *p, int n)

{   for ( ; n > 0; p++)

   {

        if (*p == 0)

            n--;

   }    

   return p;

}

 

小结:本章简单介绍的一些技巧:

字符串的数据结构

散列:速度快且易于实现

平衡树:最坏情况下也具有最好性能

                    后缀数组:遍历/二分查找

至此,《编程珠玑》此书的笔记已全部更完。非常感谢牛客网组织的读书活动以及赠予我们的宝贵书籍,让我们感受到满满诚意同时阅读也让我们受益匪浅!《编程珠玑》此书语言风格通俗易懂,讲解编程案例深入浅出,收获颇多,感谢作者!同时也感谢两位团员小伙伴,相互交流、督促,大家一起学习、提高、成长!

#读书笔记##笔记#
全部评论

相关推荐

野猪不是猪🐗:现在的环境就是这样,供远大于求。 以前卡学历,现在最高学历不够卡了,还要卡第一学历。 还是不够筛,于是还要求得有实习、不能有gap等等... 可能这个岗位总共就一个hc,筛到最后还是有十几个人满足这些要求。他们都非常优秀,各方面都很棒。 那没办法了,看那个顺眼选哪个呗。 很残酷,也很现实
点赞 评论 收藏
分享
king122:实习经历可以重点写这里这里写的清晰一点,分点写。技能特长一般是放在上面的,而且你的实习经历不能只写实现了一些简单的接口,你要去写一些难点和亮点。甚至可以写一些数字指标上去,只要你能配合业务讲出来,根据我说的这些自己简单包装一下,面试应该会更多,至于笔试和八股,那就只能纯靠自己了,对项目包装感兴趣可以找我
点赞 评论 收藏
分享
评论
3
4
分享

创作者周榜

更多
牛客网
牛客企业服务