王道机试笔记

关于时间复杂度

代码时间复杂度应在百万级别,即若算法的时间复杂度是O(n^2),则该n(往往在题目中会给出数据范围)不应大于3000,否则将会达到我们所说的千万数量级复杂度,从而程序运行时间超出题目中给出的用时限定。举例来说,我们不能在1秒时限的题目当中对10000个整数进行冒泡排序,而必须使用快速排序等时间复杂度为O(nlogn)的排序算法,否则程序很可能将会得到运行时间超出限制的评判结果。

第二章 经典入门

排序

scanf

while (scanf ("%d",&n) != EOF) ,它在完成输入n的同时又完成了什么看起来令人迷惑的条件判断?要回答这个问题,我们首先要明确两点:
第一,scanf函数是有返回值的(尽管大多时候都会被人忽略),它将返回被输入函数成功赋值的变量个数。在此例中,若成功完成输入并对n赋值,scanf函数的返回值即为1。我们即是通过该返回值来完成循环条件的判断的。
第二,我们也要注意到,该例题面中明确了输入数据会有多组,我们将要对每组输入都输出相应答案。并且,事先我们并不知道将会有多少组数据被输入到程序中。
于是,我们可以使用该循环测试条件来完成对输入是否结束的判断。过程如下:
如仍有测试数据未被测试完,那么该组测试的开头一行将如题面所说,为一个整数(n),其将会被scanf语句赋值给变量n,那么scanf返回值为1,不等于EOF(-1),循环条件成立,程序进入循环体,执行程序;
如果输入已经到达结尾(输入文件到达末尾或在命令台输入中输入Ctrl+z),scanf函数无法再为变量n赋值,于是scanf函数返回EOF(end of file)。至此,循环条件不成立,程序跳过循环体,执行return 0,使程序正常的结束。该循环判断条件既保证了可以对多组测试数据进行计算,同时又使程序在输入结束后能够正常的退出。反之,如果我们不使用循环,程序在处理完一组数据后就会退出,后续测试数据无法被正常的处理。但若我们使用死循环而不加以任何退出条件,那么程序在处理完所有的测试数据后仍就不能正常的退出,虽然程序已经输出了所有测试数据的答案,但评判系统还是会认为程序在限制时间内无法运行完毕,于是返回超时的评判结果。正因为如此,初学者很容易因为这个原因,出现莫名其妙的程序超时。所以,我们必须使用该循环判断条件来完成以上两个功能。
值得一提的是,若输入为字符串而程序采用gets()的方法读入,则相同功能的循环判断语句为while(gets(字符串变量))。

排序

若冒泡排序的数据复杂度太大,则可以使用c++的快速排序函数sort()
在头文件方面包含了algorithm头文件,并使用using namespace std语句声明了我们将会使用标准命名空间(sort被定义在其中)。此例中,sort函数的两个参数代表待排序内存的起始地址和结束地址(sort函数还有另外的重载形式,在后文中会有所提及),在此例中起始地址为buf,结束地址为buf + n。该函数调用完成后,buf数组中的数字就已经通过快速排序升序排列。我们只需对其输出即可。

#include <stdio.h>
#include <algorithm>
using namespace std;
    sort    (buf,buf + n); //使用该重载形式,表明将要使用自己定义的排列规则

假如我们将要对给定的数组进行降序排列该如何操作

#include <stdio.h>
#include <algorithm>
using namespace std; 
bool cmp (int x,int y) 
{ //定义排序规则 
    return x > y; 
    } 
int main () 
{  
    int n;   
    int buf[100];  
    sort    (buf,buf + n,cmp); //使用该重载形式,我们表明将要使用自己定义的排列规则
    }

如该代码所示,我们新定义了一个cmp函数,来实现对于新的排序规则的定义。关于cmp函数的定义规则我们只需简单的记得,当cmp的返回值为true时,即表示cmp函数的第一个参数将会排在第二个参数之前(即在我们定义的规则中,cmp表示第一个参数是否比第二个参数大,若是,则排在前面)。为了实现降序排列,我们只要判断两个参数的大小,当第一个参数比第二个参数大时返回true。然后,只需要调用sort函数的另一种重载方式:sort(排序起始地址,排序
结束地址,比较函数),如代码所示,我们便完成了对我们给定的内存区间的降序排列。

对一些由基本类型组成的结构体进行排序。

#include <stdio.h>
#include <algorithm>
#include <string.h>
using namespace std; 
struct E {
    char name[101];
    int age;  
    int score; 
    }buf[1000]; 
bool cmp(E a,E b) { 
    //实现比较规则 
    if (  a.score != b.score) 
        return a.score < b.score;  
        //若分数不相同则分数低者在前
    int tmp = strcmp(a.name,b.name);  
    if (tmp != 0) 
        return tmp < 0; 
        //若分数相同则名字字典序小者在前 
    else 
        return a.age < b.age; //若名字也相同则年龄小者在前
    }
int main () 
{  
    int n;  
    while (scanf ("%d",&n) != EOF) 
    { 
        for (int i = 0;i < n;i ++) 
        {
            scanf ("%s%d%d",buf[i].name,&buf[i].age,&buf[i].score);
        }    // 输入
        sort(buf,buf + n,cmp); //利用自己定义的规则对数组进行排序
    }
    return 0}  

同样的,与编写cmp类似,我们也可以直接定义该结构体的小于运算符来说明排序规则。

#include <stdio.h>
#include <algorithm>
#include <string.h>
using namespace std; 
struct E {  
    char name[101];  
    int age;  
    int score;  
    bool operator < (const E &b  ) const { //利用C++算符重载直接定义小于运算符
        if (score != b.score) 
            return score < b.score; 
        int tmp = strcmp(name,b.name); 
        if (tmp != 0) 
            return tmp < 0; 
        else return age < b.age;  
        } 
}buf[1000]
int main () {  
    int n;
    while (scanf ("%d",&n) != EOF) { 
         for (int i = 0;i < n;i ++) {    
             scanf("%s%d%d",buf[i].name,&buf[i].age,&buf[i].score);     
         }    
         sort(buf,buf + n);  
         for (int i = 0;i < n;i ++) { 
             printf ("%s %d %d\n",buf[i].name,buf[i].age,buf[i].score);    
         }     
         }  
     return 0; 
} 

由于已经指明了结构体的小于运算符,计算机便知道了该结构体的定序规则(sort函数只利用小于运算符来定序,小者在前)。于是,我们在调用sort时,便不必特别指明排序规则(即不使用第三个参数),只需说明排序起始位置和结束位置即可。虽然,该方法与定义cmp函数的方法类似,我个人还是建议使用第二种方法。这样,首先可以对重载算符的写法有一定的了解;其次,在今后使用标准模板库时,该写法也有一定的用处。后文中,将会广泛使用该方法编写例程。若读者坚持使用定义cmp函数的写法,只需自行将重载小于运算符中的语句转化为定义cmp函数函数体的相关语句即可,并在调用sort函数时加上第三个参数cmp

日期问题

两个日期间的天数差
解决这类区间问题有一个统一的思想——把原区间问题统一到起点确定的区间问题上去。
我们不妨把问题统一到特定日期与一个原点时间(如0000年1月1日)的天数差,当要求两个特定的日期之间的天数差时,我们只要将它们与原点日期的天数差相减,便能得到这两个特定日期之间的天数差(必要时加绝对值)。这样做有一个巨大的好处——预处理。我们可以在程序真正开始处理输入数据之前,预处理出所有日期与原点日期之间的天数差并保存起来。当数据真正开始输入时,我们只需要用O(1)的时间复杂度将保存的数据读出,稍加处理便能得到答案。值得一提的是,预处理也是空间换时间的重要手段

另外,日期类问题有一个特别需要注意的要点——闰年,每逢闰年2月将会有29天,这对我们计算天数势必会产生重大的影响。这里,我们必须明确闰年的判断规则——当年数不能被100整除时若其能被4整除则为闰年,或者其能被400整除时也是闰年。用逻辑语言表达出来即为Year % 100 != 0 && Year % 4 == 0 || Year % 400 == 0,当逻辑表达式为true时,其为闰年;反之则不是闰年。从中我们也可以看出,闰年并不严格的按照四年一次的规律出现,在某种情况下也可能出现两个相邻闰年相隔八年的情况(如1896年与1904年)。所以,这里我们推荐严格按照上述表达式来判断某一年是否是闰年,而不采用某一个闰年后第四年又是闰年的规则

#include <stdio.h>
#define ISYEAP(x) x % 100 != 0 && x % 4 == 0 || x % 400 == 0 ? 1 : 0 // 定义宏判断是否是闰年,方便计算每月天数
int dayOfMonth[13][2] = {  
    0,0,  
    31,31, 
    28,29,  
    31,31,  
    30,30,  
    31,31,  
    30,30,  
    31,31,  
    31,31,  
    30,30,  
    31,31,  
    30,30,  
    31,31 
    }; //预存每月的天数,注意二月配合宏定义作特殊处理
struct Date { //日期类,方便日期的推移 
    int Day;  
    int Month;  
    int Year;  
    void nextDay() { //计算下一天的日期
        Day ++; 
        if (  Day > dayOfMonth[Month][ ISYEAP(Year) ]) { //若日数超过了当月最大日数   
            Day = 1;    
            Month ++; //进入下一月   
            if (Month > 12) { //月数超过12     
                Month = 1;     
                Year ++;  //进入下一年   
            }      
        }     
    } 
}; 
int buf[5001][13][32]; //保存预处理的天数
int Abs(int x) { //求绝对值 
    return x < 0 ? -x : x; 
    } 
int main () {  
    Date tmp;  
    int cnt = 0; //天数计数 
    tmp.Day = 1;  
    tmp.Month = 1;  
    tmp.Year = 0; //初始化日期类对象为0年1月1日 
    while(tmp.Year != 5001) { //日期不超过5000年
        buf[tmp.Year][tmp.Month][tmp.Day] = cnt; //将该日与0年1月1日的天数差保存起来
        tmp.nextDay(); //计算下一天日期
        cnt ++; //计数器累加,每经过一天计数器即+1,代表与原点日期的间隔又增加一天 
        }  
    int d1 , m1 , y1;  
    int d2 , m2 , y2;  
    while (scanf ("%4d%2d%2d",&y1,&m1,&d1) != EOF) 
    { 
        scanf ("%4d%2d%2d",&y2,&m2,&d2); //读入要计算的两个日期
        printf("%d\n",Abs(buf[y2][m2][d2] - buf[y1][m1][d1]) + 1); //用预处理的数据计算两日期差值,注意需对其求绝对值 
        }  
    return 0; 
} 

该代码展示了我们处理这类问题的基本方法。首先定义了一个类,不仅可以用来表示日期,还能够自动的计算出下一个日期。我们利用该类,一方面不断计算出当前日期的下一个日期,另一方面累加计数器,计算当前日期与原点日期的天数差,并将其保存在内存中,有了这些预处理出的数据,我们在计算两个特定日期差时,只要将两个日期与原点时间日期差相减,取绝对值,并根据题目需要加上1(“两个日期是连续的我们规定他们之间的天数为两天”),就能得出答案。

我们也可以考虑一下,假如问题需要我们输出某个特定的日期,是那年的第几天,我们该怎样利用我们已经得到的数据来计算呢?我们只需要用该日期与原点日期的天数减去那年元旦与原点日期便可得知该日期是当年的第几天。在日期类中,为了判断该年是否是闰年,我们定义了一个宏。利用上文中提到的逻辑表达式来判断该年是否是闰年,并且根据该表达式的逻辑值使宏表达式为1或0,通过该宏表达式选择保存每月天数的数组的不同列来取得该月应该具有的天数,从而保证了当闰年时,2月29日存在。

我们将buf[5001][13][32]这个相对比较耗费内存的数组定义成全局变量,这不是偶然的。由于需要耗费大量的内存,若在main函数(其它函数也一样)之中定义该数组,其函数所可以使用的栈空间将不足以提供如此庞大的内存,出现栈溢出,导致程序异常终止。所以,今后凡是涉及此类需要开辟大量内存空间的情况,我们都必须在函数体外定义,即定义为全局变量。或者在函数中使用malloc等函数动态申请变量空间。

输入一个日期,要求输出该日期为星期几

#include <stdio.h>
#include <string.h>
#define ISYEAP(x) x % 100 != 0 && x % 4 == 0 || x % 400 == 0 ? 1 : 0 
int dayOfMonth[13][2] = {  
    0,0,  
    31,31,  
    28,29,  
    31,31,  
    30,30,  
    31,31,  
    30,30,  
    31,31,  
    31,31,  
    30,30, 
    31,31,  
    30,30,  
    31,31 
    }; 
struct Date {  
    int Day;  
    int Month;  
    int Year;  
    void nextDay() { 
        Day ++; 
        if (Day > dayOfMonth[Month][ ISYEAP(Year) ]) {    
            Day = 1;    
            Month ++;    
            if (Month > 12) {     
                Month = 1;     
                Year ++;    
            }      
        }     
    } 
}; 
int buf[3001][13][32]; 
char monthName[13][20] = {  "",  "January",   "February",   "March",   "April",   "May",   "June",   "July",   "August",   "September",   "October",   "November",  
 "December"}; //月名每个月名对应下标1到12 
 char weekName[7][20] = {  "Sunday",   "Monday",   "Tuesday",   "Wednesday",   "Thursday",   "Friday",   "Saturday"}; //周名每个周名对应下标0到6 
 int main () {  
     Date tmp;  
     int cnt = 0;  
     tmp.Day = 1;  
     tmp.Month = 1;  
     tmp.Year = 0;  
     while(tmp.Year != 3001) { 
         buf[tmp.Year][tmp.Month][tmp.Day] = cnt; 
         tmp.nextDay(); 
         cnt ++;  
     } //以上与上题一致,预处理出每一天与原点日期的天数差 
     int d ,m   , y;  
     char s[20];  
     while (scanf ("%d%s%d",&d,s,&y  ) != EOF) { 
         for (m = 1;m <= 12;m ++) {    
             if (strcmp(s,monthName[m]) == 0) {     
                 break; //将输入字符串与月名比较得出月数   
             }      
         }    
     int    days = buf[y][m  ][d  ] - buf[2012][7][16]; //计算给定日期与今日日期的天数间隔(注意可能为负) 
     days += 1; //今天(2012.7.16)为星期一,对应数组下标为1,则计算1经过days天后的下标
     puts(weekName[(days % 7 + 7) % 7]); //将计算后得出的下标用7对其取模,并且保证其为非负数,则该下标即为答案所对应的下标,输出即可 
     }  
     return 0; 
}

Hash的应用

排版题

先排版后输出
输出图形的规律性主要体现在由内而外的各个环上,而这与我们的输出顺序又不太契合(从上至下,从左至右),于是我们不容易将该图形存在的规律直接应用到输出当中,所以我们需要使用刚才所提到的办法——先排版后输出。

先用一个二维数组来保存将要输出的字符阵列,并在该数组上首先完成排版。因为没有了输出时从上至下、从左至右的顺序限制,我们能更加随意的按照自己的需要或者图形的规律来依次输出图形,从而完成题目要求。

#include <stdio.h>
int main () {  
    int outPutBuf[82][82]; //用于预排版的输出缓存 
    char a , b; //输入的两个字符 
    int n; //叠框大小 
    bool firstCase = true; //是否为第一组数据标志,初始值为true  
    while (scanf ("%d %c %c",&n  ,&a  ,&b  ) == 3) { 
        if (firstCase == true) { //若是第一组数据   
        firstCase = false; //将第一组数据标志标记成false      
    }    
    else printf("\n"); //否则输出换行
    for (int i = 1,j = 1;i <= n;i += 2,j ++) { //从里至外输出每个圈   
        int x = n / 2 + 1 , y = x;     
        x -= j - 1; y -= j - 1; //计算每个圈右上角点的坐标   
        char c = j % 2 == 1 ? a : b; //计算当前圈需要使用哪个字符   
        for (int k = 1;k <= i;k ++) { //对当前圈进行赋值    
            outPutBuf[x + k - 1][y] = c; //左边赋值    
            outPutBuf[x][y   + k - 1] = c; //上边赋值    
            outPutBuf[x + i - 1][y + k - 1] = c; //右边赋值    
            outPutBuf[x + k - 1][y + i - 1] = c; //下边赋值
        }      
    }    
    if (n != 1) { //注意当n为1时不需此步骤   
    outPutBuf[1][1] = ' ';    
    outPutBuf[n][1] = ' ';    
    outPutBuf[1][n] = ' ';    
    outPutBuf[n][n  ] = ' '; //将四角置为空格     
    }    
    for (int i = 1;i <= n;i ++) {    
        for (int j = 1;j <= n;j ++) {     
            printf("%c",outPutBuf[i][j  ]);    
        }    
        printf("\n");   
        } //输出已经经过排版的在输出缓存中的数据 
    }  
    return 0; 
} 

查找

在查找某特定元素是否存在以外,二分查找还有另一类非常重要的运用,即定界。思考如下问题,在一个升序有序的数组中,确定一个下标点,使在这个下标点之前(包括该下标点)的数字均小于等于目标数字(该目标数字一定大于等于数组中最小的数字),而数组的其余部分均大于目标数字,我们该如何编写程序。要完成这个问题的解答,我们首先要对二分查找原理有一定的了解,读者可以自行考虑如何运用二分查找确定该边界点,这里仅给出代码以供参考。

//存在一个升序有序的数组buf,其大小为size,目标数字为target 
int base = 0 , top = size; //初始情况与二分查找一致 
while (base <= top) { //二分循环条件与二分查找一致
    int mid = (base + top) / 2;
    if (buf[mid] <= target) 
        base = mid + 1; //符合前一部分数字规定
    else top = mid - 1; //否则 
}  
int ans = top; //最后,top即为我们要求的数字数组下标,buf[top]为该数字本身

贪心算法

选择性价比最高的商品
选择结束最早的节目

第三章 数据结构

定义

#include <stack>
stack<int> S; 

压栈取栈顶弹栈

S.push(i); 
int x = S.top(); 
S.pop();

括号匹配问题

按照从左至右的顺序遍历字符串,并将遇到的所有左括号都放入堆栈中等待匹配;若在遍历过程中遇到一个右括号,由于按照从左向右的顺序遍历字符串,若此时堆栈非空,那么栈顶左括号即为与其匹配的左括号;相反,若堆栈为空,则表示在其之前不存在未被匹配的左括号,匹配失败。

#include <stdio.h>
#include <stack> 
using namespace std; 
stack<int> S; //定义一个堆栈
char str[110]; //保存输入字符串
char ans[110]; //保存输出字符串
int main () {  
    while (scanf ("%s",str)  != EOF) { //输入字符串
        int i; 
        for (i = 0;str[i] != 0;i ++) { //从左到右遍历字符串   
            if (str[i] == '(') { //若遇到左括号
                S.push(i); //将其数组下标放入堆栈中    
                ans[i] = ' '; //暂且将对应的输出字符串位置改为空格   
            }    
            else if (str[i] == ')') { //若遇到右括号    
                if (S.empty() == false) { //若此时堆栈非空     
                    S.pop(); //栈顶位置左括号与其匹配,从栈中弹出该已经匹配的左括号     
                    ans[i] = ' '; //修改输出中该位置为空格    
                }     
                else ans[i] = '?'; //若堆栈为空,则无法找到左括号与其匹配,修改输出中该位置为?    
            }    
            else ans[i] = ' '; //若其为其它字符,与括号匹配无关,则修改输出为空格     
        }    
        while(!S.empty()) { //当字符串遍历完成后,尚留在堆栈中的左括号无法匹配   
            ans[ S.top() ] = '$'; //修改其在输出中的位置为$    
            S.pop(); //弹出     
        }    
        ans[i] = 0; //为了使输出形成字符串,在其最后一个字符后添加一个空字符
        puts(str);  //输出原字符串
        puts(ans); //输出答案字符串 
    }  
    return 0; 
}

表达式求值

1.设立两个堆栈,一个用来保存运算符,另一个用来保存数字。
2.在表达式首尾添加标记运算符,该运算符运算优先级最低。
3.从左至右依次遍历字符串,若遍历到运算符,则将其与运算符栈栈顶元素进行比较,若运算符栈栈顶运算符优先级小于该运算符或者此时运算符栈为空,则将该运算符压入堆栈。遍历字符串中下一个元素。
4.若运算符栈栈顶运算符优先级大于该运算符,则弹出该栈顶运算符,再从数字栈中依次弹出两个栈顶数字,完成弹出的运算符对应的运算得到结果后,再将该结果压入数字栈,重复比较此时栈顶运算符与当前遍历到的运算符优先级,视其优先级大小重复步骤3或步骤4。
5.若遍历到表达式中的数字,则直接压入数字栈。
6.若运算符堆栈中仅存有两个运算符且栈顶元素为我们人为添加的标记运算符,那么表达式运算结束,此时数字堆栈中唯一的数字即为表达式的值。

#include <stack> 
#include <stdio.h>
using namespace std; char str[220]; //保存表达式字符串
int mat[][5] = { //优先级矩阵,若mat[i][j] == 1,则表示i号运算符优先级大于j号运算符,运算符编码规则为+为1号,-为2号,*为3号,/为4号,我们人为添加在表达式首尾的标记运算符为0号 
    1,0,0,0,0,  
    1,0,0,0,0,  
    1,0,0,0,0,  
    1,1,1,0,0,  
    1,1,1,0,0, 
};
stack<int> op; //运算符栈,保存运算符编号
stack<double> in; //数字栈,运算结果可能存在浮点数,所以保存元素为
double void getOp(bool &reto,int &retn,int &i) { //获得表达式中下一个元素函数,若函数运行结束时,引用变量reto为true,则表示该元素为一个运算符,其编号保存在引用变量retn中;否则,表示该元素为一个数字,其值保存在引用变量retn中.引用变量i表示遍历到的字符串下标 
    if (i == 0 && op.  empty() == true) { //若此时遍历字符串第一个字符,且运算符栈为空,我们人为添加编号为0的标记字符
        reto = true; //为运算符
        retn = 0; //编号为0 
        return; //返回 
    }  
    if (str[i] == 0) { //若此时遍历字符为空字符,则表示字符串已经被遍历完
        reto = true; //返回为运算符
        retn = 0; //编号为0的标记字符
        return; //返回 
    }  
    if (str[i] >= '0' && str[i] <= '9') { //若当前字符为数字
        reto = false; //返回为数字 
    }  
    else { //否则
        reto = true; //返回为运算符
        if (str[i] == '+') { //加号返回1    
            retn = 1;      
        }    
        else if (str[i] == '-') { //减号返回2    
            retn = 2;      
        }    
        else if (str[i] == '*') { //乘号返回3    
            retn = 3;      
        }    
        else if (str[i] == '/') { //除号返回4    
            retn = 4;      
        }    
        i += 2;//i递增,跳过该运算字符和该运算字符后的空格
        return; //返回 
    }  
    retn = 0; //返回结果为数字 
    for (;str[i] != ' ' && str[i] != 0;i ++) { //若字符串未被遍历完,且下一个字符不是空格,则依次遍历其后数字,计算当前连续数字字符表示的数值
        retn *= 10; 
        retn += str[i] - '0';  
    } //计算该数字的数字值 
    if (str[i] == ' ') //若其后字符为空格,则表示字符串未被遍历完
        i ++; //i递增.跳过该空格 
    return;//返回
}
int main () {  
    while(gets(str)) { //输入字符串,当其位于文件尾时,gets返回0 
        if (str[0] == '0' && str[1] == 0) 
            break; //若输入只有一个0,则退出
        bool retop;
        int retnum; //定义函数所需的引用变量
        int idx = 0; //定义遍历到的字符串下标,初始值为0 
        while(!op.empty()) op.pop(); 
        while(!in.empty()) in.pop(); //清空数字栈,和运算符栈
        while(true) { //循环遍历表达式字符串   
            getOp(retop,retnum,idx); //获取表达式中下一个元素   
            if (retop == false) { //若该元素为数字    
                in.push((double)retnum); //将其压入数字栈中   
            }    
            else { //否则    
                double tmp;     
                if (op.empty() == true || mat[retnum][op.top()] == 1) { 
                    op.push(retnum);     
                }//若运算符堆栈为空或者当前遍历到的运算符优先级大于栈顶运算符,将该运算符压入运算符堆栈    
                else { //否则     
                    while(mat[retnum][op.top()] == 0) { //只要当前运算符优先级小于栈顶元素运算符,则重复循环      
                        int ret = op.top(); //保存栈顶运算符      
                        op.pop(); //弹出      
                        double b = in.top();       
                        in.pop();       
                        double a = in.top();       
                        in.pop(); //从数字堆栈栈顶弹出两个数字,依次保存在遍历a.b中      
                        if (ret == 1) 
                            tmp = a + b;       
                        else if (ret == 2) 
                            tmp = a - b;       
                        else if (ret == 3) 
                            tmp = a * b;       
                        else 
                            tmp = a / b; //按照运算符类型完成运算      
                        in.push(tmp); //将结果压回数字堆栈     
                    }      
                    op.push(retnum); //将当前运算符压入运算符堆栈    
                }    
            }    
            if (op.  size() == 2 && op.  top() == 0) 
                break; //若运算符堆栈只有两个元素,且其栈顶元素为标记运算符,则表示表达式求值结束      
        }    
        printf("%.2f\n",in.  top()); //输出数字栈中唯一的数字,即为答案 
    }  
    return 0; 
}

哈夫曼树

为了方便快捷高效率的求得集合K中权值最小的两个元素,我们需要使用堆数据结构。它可以以O(logn)的复杂度取得n个元素中的最小元素。为了绕过对堆的实现,我们使用标准模板库中的相应的标准模板——优先队列

建堆

#include <queue> 
using namespace std;
priority_queue<int> Q; 

这样建立的堆其默认为大顶堆,即我们从堆顶取得的元素为整个堆中最大的元素。而在求哈夫曼树中,我们恰恰需要取得堆中最小的元素,于是我们使用如下语句定义一个小顶堆

#include <queue> 
using namespace std;
priority_queue<int , vector<int> , greater<int> > Q; 

堆的操作

Q.push(x); //将元素x放入堆Q中。
int a = Q.top(); //取出堆顶元素,即最小的元素保存在a中。
Q.pop(); //弹出堆顶元素,取出后堆会自动调整为一个新的小顶堆。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值