开-O2
#include<stack>栈 <库>
C++stack(堆栈)是一个容器的改编,它实现了一个先进后出的数据结构(FILO)
使用该容器时需要包含#include<stack>头文件;
定义stack对象的示例代码如下:
stack<int>s1;
stack<string>s2;
stack的基本操作有:
1.入栈:如s.push(x);
2.出栈:如 s.pop().注意:出栈操作只是删除栈顶的元素,并不返回该元素。
3.访问栈顶:如s.top();
4.判断栈空:如s.empty().当栈空时返回true。
5.访问栈中的元素个数,如s.size();
下面举一个简单的例子:
[cpp] view plain copy
1. #include<iostream>
2. #include<stack>
3. using namespace std;
4. int main(void)
5. {
6. stack<double>s;//定义一个栈
7. for(int i=0;i<10;i++)
8. s.push(i);
9. while(!s.empty())
10. {
11. printf("%lf\n",s.top());
12. s.pop();
13. }
14. cout<<"栈内的元素的个数为:"<<s.size()<<endl;
15. return 0;
16. }
http://www.360doc.com/content/13/0912/17/3373961_314006037.shtml
http://blog.csdn.net/chao_xun/article/details/8037420
#include<queue>队列 <库>
一.queue模版类的定义在<queue>头文件中。
queue与stack模版非常类似,queue模版也需要定义两个模版参数,一个是元素类型,一个是容器类型,元素类型是必要的,容器类型是可选的,默认为dqueue类型。
定义queue对象的示例代码如下:
queue<int>q1;
queue<double>q2;
queue的基本操作有:
1.入队:如q.push(x):将x元素接到队列的末端;
2.出队:如q.pop() 弹出队列的第一个元素,并不会返回元素的值;
3,访问队首元素:如q.front()
4,访问队尾元素,如q.back();
5,访问队中的元素个数,如q.size();
二.优先队列
在<queue>头文件中,还定义了一个非常有用的模版类priority_queue(优先队列),优先队列与队列的差别在于优先队列不是按照入队的顺序出队,而是按照队列中元素的优先权顺序出队(默认为大者优先,也可以通过指定算子来指定自己的优先顺序)。
priority_queue模版类有三个模版参数,元素类型,容器类型,比较算子。其中后两个都可以省略,默认容器为vector,默认算子为less,即小的往前排,大的往后排(出队时序列尾的元素出队)。
定义priority_queue对象的示例代码如下:
priority_queue<int>q1;
priority_queue<pair<int,int>>q2;
priority_queue<int,vector<int>,greater<int>>q3;//定义小的先出队
priority_queue的基本操作均与queue相同
初学者在使用priority_queue时,最困难的可能就是如何定义比较算子了。如果是基本数据类型,或已定义了比较运算符的类,可以直接用STL的less算子和greater算子——默认为使用less算子,即小的往前排,大的先出队。如果要定义自己的比较算子,方法有多种,这里介绍其中的一种:重载比较运算符。优先队列试图将两个元素x和y代入比较运算符(对less算子,调用x<y,对greater算子,调用x>y),若结果为真,则x排在y前面,y将先于x出队,反之,则将y排在x前面,x将先出队。
看下面这个简单的示例:
[cpp] view plain copy
1. #include<iostream>
2. #include<queue>
3. #include<stdlib.h>
4. using namespace std;
5. class T
6. {
7. public:
8. int x,y,z;
9. T(int a,int b,int c):x(a),y(b),z(c)
10. {
11. }
12. };
13. bool operator<(const T&t1,const T&t2)
14. {
15. return t1.z<t2.z;
16. }
17. int main(void)
18. {
19. priority_queue<T>q;
20. q.push(T(4,4,3));
21. q.push(T(2,2,5));
22. q.push(T(1,5,4));
23. q.push(T(3,3,6));
24. while(!q.empty())
25. {
26. T t=q.top();
27. q.pop();
28. cout<<t.x<<" "<<t.y<<" "<<t.z<<endl;
29. }
30. system("Pause");
31. return 1;
32. }
输出结果为
注意这里是按照z的顺序从大到小出队的。
如果我们把上述例子中的比较运算符重载改为:
booloperator<(const T &t1,const T &t2)
{
returnt1.z>t2.z;
}
那么得到的输出结果将会按照z的顺序从小到大出队
http://blog.csdn.net/chao_xun/article/details/8037438
链表
单项链表,双向链表,循环链表
前段时间学习了数据结构的部分知识,然后到上个星期别人问相关问题的饿时候发现自己对链表的知识都有些模糊了,主要还是有些细节的地方记不太清楚,所以就萌生了写这篇博客的想法,一是要重新复习一下链表的相关知识,二呢用C++重新写一遍,熟悉C++语言。之前用C语言实现链表操作的时候也写过几篇博客,但是重点都是体现在代码部分,示意图都是从网站上下过来的或是别人博客上摘过来的,这次自己用Processon在网页上重新画了链表操作的过程示意图,根据自己的理解画的一些简易图,然后感觉自己对链表要注意的细节更加清楚了。写这个博客主要是自己对知识点的复习,理解的一个检查,如果能帮助某些志同道合的人那就更加欣慰了。
这里我主要分四个知识点来介绍链表,分别是单向链表、单向循环链表、双向链表和双向循环链表。操作包括链表的初始化、链表的创建、链表插入结点、链表删除结点以及链表的删除,也就是释放内存。
源代码链接:
单向链表
- 单向链表就是通过每个结点的指针指向下一个结点从而链接起来的结构。下面看下单向链表的示意图(以下图片全是原创)
- 单向链表的初始化:这里我所讲的链表都是头结点不参与计算的,也就是说第一个结点都是头结点后面的第一个结点。所以我要先申明一点,这里我把链表的初始化放在了构造函数部分,然后析构函数负责释放头结点的内存。
- 单向链表的创建过程:链表的创建就是添加结点到链表的最后,开始是添加一个结点到head结点后面,然后添加一个结点到上次添加的结点后面,每次新建的结点的指针总是指向NULL指针。从上面的示意图可以看出,我们需要一个辅助指针一直指向最后一个结点,这个辅助结点就是为了让每次添加的结点都放置在最后一个位置。
- 单向链表插入结点过程:源代码中的的插入结点函数我设置了一个指定位置,就是在指定位置插入结点。首先,通过位置变量position让ptemp结点移动到要插入位置的前一个位置,然后接下来的过程就是和创建链表的过程是一样的,把新建的结点添加到ptemp的后面。这里变量position可以从1到链表长度加1,意思就是如果不算头结点的话有3个结点,那你的position变量就可以从1到4,这是因为ptemp指针可以到第3个结点的位置,所以新建结点的位置就可以到4了。
- 单向链表删除结点过程:源代码中的删除结点函数也有一个指定位置变量,为了删除指定位置的结点。和插入结点一样通过变量position把ptemp移动到要删除结点的前一个位置,然后让ptemp结点中的指针指向要删除结点后面的一个结点,也就是ptemp结点的下一个的下一个结点,虽然这个结点可能为空,但是程序还是正常运行。但是这里和插入结点不同的是变量position只能从1到链表的长度,是因为ptemp移动到最后一个结点的时候,它的下一个结点为空,所以不不需要参与删除了。
单向循环链表
1.单向循环链和单向链表有点相似的地方,就是都是通过结点的指针指向下一个结点,然后这样连接起来,但是有一个不同的地方就是单向链表的最后一个结点的指针指向NULL指针,而单向循环链表的最后一个结点的指针指向的是头结点,这样构成一个循环结点的环。下面是单向循环链表的示意图:
2.单向循环链表的初始化:从上面的示意图可以知道,单向循环链表最后一个结点的指针是指向头结点的,那么当只有一个结点的时候就是头结点的指针指自己。
3.单向循环链表的创建过程:和单向链表的创建过程相似,区别就是最后一个结点的指针是指着头结点的。所以程序实现的时候每次新建一个结点的时候都是让它的指针指向头结点。
4.单向循环链表插入结点过程:因为这个时候是往链表中插入结点,结点已经构成一个环了,所以直接让新插入结点的指针指向下一个结点就行。就算插入的是最后一个位置,让它的指针指向下一个结点也符合条件,因为最后一个位置的下一个结点就是头结点。
5.单向循环链表删除结点过程:结合单向链表删除结点过程和上面单向循环链表插入结点过程的解释,那么这里就不难理解了。
双向链表
1.听名字可能就能猜到双向链表就是链表结点包含两个指针,一个指针是指向下一个结点的,另一个指针当然就是指向上一个结点的。这样的链表就可以通过第一个结点找到最后一个结点,当然也可以通过最后一个结点找到第一个结点,因为它是双向的,下面看下双向链表的示意图:
2.双向链表的初始化:由于这里的链表头结点不参与计算,所以头结点的pPre指针是一直指向NULL指针的。
3.双向链表的创建过程:由于双向链表的每个结点包含两个指针那么这个时候我们就要小心处理好每一个指针的指向,要不然会有很多意想不到的错误。同样的,和单向链表的创建过程一样,需要一个辅助指针来指向最后一个结点,然后每新建一个结点,这个结点的pNext指针都是指向NULL指针的,pPre指针指向上一个结点(这是和单向链表不同的地方),然后让上一个指针的pNext指向新建的结点,这样整个链表就连接起来了。
4.双向链表插入结点过程:知道了双向链表的创建过程,那么插入结点的过程就大同小异了,有一点需要特别注意的就是这里的变量position范围也是从1到链表长度加1,但是如果待插入的位置是最后一个位置的话,情况就不同了,看到下面的图我们可以很好的理解,因为没新建一个结点的时候都需要处理两个指针,而且新建结点的下一个结点的pPre指针就需要指向这个新建的结点,但是有可能这个新建的结点可能就已经是最后一个结点了,那么这个时候再执行
[cpp] view plain copy
1. ptemp->pNext->pPre = pnew;
这条指令的时候就会报错了,因为ptemp->pNext已经是个NULL指针了,那空指针哪里还有pPre呢。因此在程序中要进行一次判断,看看结点是否是最后一个结点。
5.双向链表删除结点的过程:要注意的问题和插入结点一样,看看这个结点是否为NULL。这里就不重复了。
双向循环链表
1.双向循环链表相对于上面的双向链表多了循环两个字,我们就可以结合单向循环链表联想到,其实就是双向链表够成了一个环。下面先看看双向循环链表的示意图:
2.双向循环链表初始化:双向循环链表的初始化可以通过单向循环链表的初始化联想到,就是两个指针都指向头结点本身。这是因为pPre指针要指向上一个结点,而pNext指针要指向下一个结点,这个时候就只有头结点一个,所以就都指向头结点了。
3.双向循环链表的创建过程:这个也过程和上面单向循环链表都类似,就是多了一个结点指针要考虑到。
4.双向循环链表插入结点过程:和单向循环链表过程类似。
5.双向循环链表删除结点过程:这个也是和单向循环链表类似。
写了2个多小时,终于快结束了,其实加上画图和敲源代码总共花了好几天的时间。最后我想说说我对链表的一点个人理解,开始学习的时候看过去挺简单的,但是自己操作的时候就会发现遇到很多细节性的问题,所以劝解那些看的人还是动动手自己写一遍。然后呢,我想说的是,这里列出的只是链表的一些简单操作,不能说会了这些链表就都会了,当然知识也是相通的,如果真正理解了的话,那不管怎么变也不怕了。比如:链表数据的排序、然后输入结点时按顺序插入等,还有很多很多。
PS:希望大家一起学习、一起讨论、一起进步。
http://blog.csdn.net/fisherwan/article/details/25796625
单项链表的操作
有的时候,处于内存中的数据并不是连续的。那么这时候,我们就需要在数据结构中添加一个属性,这个属性会记录下面一个数据的地址。有了这个地址之后,所有的数据就像一条链子一样串起来了,那么这个地址属性就起到了穿线连结的作用。
相比较普通的线性结构,链表结构的优势是什么呢?我们可以总结一下:
(1)单个节点创建非常方便,普通的线性内存通常在创建的时候就需要设定数据的大小
(2)节点的删除非常方便,不需要像线性结构那样移动剩下的数据
(3)节点的访问方便,可以通过循环或者递归的方法访问到任意数据,但是平均的访问效率低于线性表
那么在实际应用中,链表是怎么设计的呢?我们可以以int数据类型作为基础,设计一个简单的int链表:
(1)设计链表的数据结构
[cpp] view plain copy
1. typedef struct _LINK_NODE
2. {
3. int data;
4. struct _LINK_NODE* next;
5. }LINK_NODE;
(2)创建链表
[cpp] view plain copy
1. LINK_NODE* alloca_node(int value)
2. {
3. LINK_NODE* pLinkNode = NULL;
4. pLinkNode = (LINK_NODE*)malloc(sizeof(LINK_NODE));
5.
6. pLinkNode->data = value;
7. pLinkNode->next = NULL;
8. return pLinkNode;
9. }
(3)删除链表
[cpp] view plain copy
1. void delete_node(LINK_NODE** pNode)
2. {
3. LINK_NODE** pNext;
4. if(NULL == pNode || NULL == *pNode)
5. return ;
6.
7. pNext = &(*pNode)->next;
8. free(*pNode);
9. delete_node(pNext);
10. }
(4)链表插入数据
[cpp] view plain copy
1. STATUS _add_data(LINK_NODE** pNode, LINK_NODE* pDataNode)
2. {
3. if(NULL == *pNode){
4. *pNode = pDataNode;
5. return TRUE;
6. }
7.
8. return _add_data(&(*pNode)->next, pDataNode);
9. }
10.
11. STATUS add_data(const LINK_NODE** pNode, int value)
12. {
13. LINK_NODE* pDataNode;
14. if(NULL == *pNode)
15. return FALSE;
16.
17. pDataNode = alloca_node(value);
18. assert(NULL != pDataNode);
19. return _add_data((LINK_NODE**)pNode, pDataNode);
20. }
(5)删除数据
[cpp] view plain copy
1. STATUS _delete_data(LINK_NODE** pNode, int value)
2. {
3. LINK_NODE* pLinkNode;
4. if(NULL == (*pNode)->next)
5. return FALSE;
6.
7. pLinkNode = (*pNode)->next;
8. if(value == pLinkNode->data){
9. (*pNode)->next = pLinkNode->next;
10. free(pLinkNode);
11. return TRUE;
12. }else{
13. return _delete_data(&(*pNode)->next, value);
14. }
15. }
16.
17. STATUS delete_data(LINK_NODE** pNode, int value)
18. {
19. LINK_NODE* pLinkNode;
20. if(NULL == pNode || NULL == *pNode)
21. return FALSE;
22.
23. if(value == (*pNode)->data){
24. pLinkNode = *pNode;
25. *pNode = pLinkNode->next;
26. free(pLinkNode);
27. return TRUE;
28. }
29.
30. return _delete_data(pNode, value);
31. }
(6)查找数据
[cpp] view plain copy
1. LINK_NODE* find_data(const LINK_NODE* pLinkNode, int value)
2. {
3. if(NULL == pLinkNode)
4. return NULL;
5.
6. if(value == pLinkNode->data)
7. return (LINK_NODE*)pLinkNode;
8.
9. return find_data(pLinkNode->next, value);
10. }
(7)打印数据
[cpp] view plain copy
1. void print_node(const LINK_NODE* pLinkNode)
2. {
3. if(pLinkNode){
4. printf("%d\n", pLinkNode->data);
5. print_node(pLinkNode->next);
6. }
7. }
(8)统计数据
[cpp] view plain copy
1. int count_node(const LINK_NODE* pLinkNode)
2. {
3. if(NULL == pLinkNode)
4. return 0;
5.
6. return 1 + count_node(pLinkNode->next);
7. }
http://blog.csdn.net/feixiaoxing/article/details/6848077
卡特兰数
Catalan数——卡特兰数
今天阿里淘宝笔试中碰到两道组合数学题,感觉非常亲切,但是笔试中失踪推导不出来
后来查了下,原来是Catalan数。悲剧啊,现在整理一下
一、Catalan数的定义令h(1)=1,Catalan数满足递归式:h(n) = h(1)*h(n-1) +h(2)*h(n-2) + ... + h(n-1)h(1),n>=2该递推关系的解为:h(n) = C(2n-2,n-1)/n,n=1,2,3,...(其中C(2n-2,n-1)表示2n-2个中取n-1个的组合数)
问题描述:
12个高矮不同的人,排成两排,每排必须是从矮到高排列,而且第二排比对应的第一排的人高,问排列方式有多少种?
这个笔试题,很YD,因为把某个递推关系隐藏得很深。
问题分析:
我们先把这12个人从低到高排列,然后,选择6个人排在第一排,那么剩下的6个肯定是在第二排.
用0表示对应的人在第一排,用1表示对应的人在第二排,那么含有6个0,6个1的序列,就对应一种方案.
比如000000111111就对应着
第一排:0 1 2 3 4 5
第二排:6 7 8 9 10 11
010101010101就对应着
第一排:0 2 4 6 8 10
第二排:1 3 5 7 9 11
问题转换为,这样的满足条件的01序列有多少个。
观察1的出现,我们考虑这一个出现能不能放在第二排,显然,在这个1之前出现的那些0,1对应的人
要么是在这个1左边,要么是在这个1前面。而肯定要有一个0的,在这个1前面,统计在这个1之前的0和1的个数。
也就是要求,0的个数大于1的个数。
OK,问题已经解决。
如果把0看成入栈操作,1看成出栈操作,就是说给定6个元素,合法的入栈出栈序列有多少个。
这就是catalan数,这里只是用于栈,等价地描述还有,二叉树的枚举、多边形分成三角形的个数、圆括弧插入公式中的方法数,其通项是c(2n, n)/(n+1)。
在<<计算机程序设计艺术>>,第三版,Donald E.Knuth著,苏运霖译,第一卷,508页,给出了证明:
问题大意是用S表示入栈,X表示出栈,那么合法的序列有多少个(S的个数为n)
显然有c(2n, n)个含S,X各n个的序列,剩下的是计算不允许的序列数(它包含正确个数的S和X,但是违背其它条件)。
在任何不允许的序列中,定出使得X的个数超过S的个数的第一个X的位置。然后在导致并包括这个X的部分序列中,以S代替所有的X并以X代表所有的S。结果是一个有(n+1)个S和(n-1)个X的序列。反过来,对一垢一种类型的每个序列,我们都能逆转这个过程,而且找出导致它的前一种类型的不允许序列。例如XXSXSSSXXSSS必然来自SSXSXXXXXSSS。这个对应说明,不允许的序列的个数是c(2n, n-1),因此an = c(2n, n) - c(2n, n-1)。
验证:其中F表示前排,B表示后排,在枚举出前排的人之后,对应的就是后排的人了,然后再验证是不是满足后面的比前面对应的人高的要求。
[cpp] view plain copy
1. #include <iostream>
2. using namespace std;
3.
4. int bit_cnt(int n)
5. {
6. int result = 0;
7. for (; n; n &= n-1, ++result);
8. return result;
9. }
10.
11. int main(void)
12. {
13. int F[6], B[6];
14. int i,j,k,state,ok,ans = 0;
15. for (state = 0; state < (1 << 12); ++state)
16. {
17. if (bit_cnt(state) == 6)
18. {
19. i = j = 0;
20. for (int k = 0; k < 12; ++k)
21. {
22. if(state&(1<<k))
23. F[i++] = k;
24. else
25. B[j++] = k;
26. }
27. ok = 1;
28. for (k = 0; k < 6; ++k)
29. {
30. if (B[k] < F[k])
31. {
32. ok = 0;
33. break;
34. }
35. }
36. ans += ok;
37. }
38. }
39. cout << ans << endl;
40. return 0;
41. }
结果:132
而c(12, 6)/7 = 12*11*10*9*8*7/(7*6*5*4*3*2)= 132
注意:c(2n, n)/(n+1) = c(2n, n) - c(2n, n-1)
估计出题的人也读过<<计算机程序艺术>>吧。
PS:
另一个很YD的问题:
有编号为1到n(n可以很大,不妨在这里假定可以达到10亿)的若干个格子,从左到右排列。
在某些格子中有一个棋子,不妨设第xi格有棋子(1<=i<=k,1<=k<=n)
每次一个人可以把一个棋子往左移若干步,但是不能跨越其它棋子,也要保证每个格子至多只有一个棋子。
两个人轮流移动,移动不了的为输,问先手是不是有必胜策略。
三、Catalan数的典型应用:
1、括号化问题。矩阵链乘: P=A1×A2×A3×……×An,依据乘法结合律,不改变其顺序,只用括号表示成对的乘积,试问有几种括号化的方案?
一个有n个X和n个Y组成的字串,且所有的部分字串皆满足X的个数大于等于Y的个数。以下为长度为6的dyck words:
XXXYYY XYXXYY XYXYXY XXYYXY XXYXYY
将上例的X换成左括号,Y换成右括号,Cn表示所有包含n组括号的合法运算式的个数:
((())) ()(()) ()()() (())() (()())
2、将多边行划分为三角形问题。将一个凸多边形区域分成三角形区域(划分线不交叉)的方法数?
类似:在圆上选择2n个点,将这些点成对连接起来使得所得到的n条线段不相交的方法数?
3、出栈次序问题。一个栈(无穷大)的进栈序列为1、2、3、...、n,有多少个不同的出栈序列?
类似:有2n个人排成一行进入剧场。入场费5元。其中只有n个人有一张5元钞票,另外n人只有10元钞票,剧院无其它钞票,问有多少中方法使得只要有10元的人买票,售票处就有5元的钞票找零?(将持5元者到达视作将5元入栈,持10元者到达视作使栈中某5元出栈)
类似:一位大城市的律师在他住所以北n个街区和以东n个街区处工作,每天她走2n个街区去上班。如果他从不穿越(但可以碰到)从家到办公室的对角线,那么有多少条可能的道路?
分析:对于每一个数来说,必须进栈一次、出栈一次。我们把进栈设为状态‘1’,出栈设为状态‘0’。n个数的所有状态对应n个1和n个0组成的2n位二进制数。由于等待入栈的操作数按照1‥n的顺序排列、入栈的操作数b大于等于出栈的操作数a(a≤b),因此输出序列的总数目=由左而右扫描由n个1和n个0组成的2n位二进制数,1的累计数不小于0的累计数的方案种数。
4、给顶节点组成二叉树的问题。
给定N个节点,能构成多少种形状不同的二叉树?
(一定是二叉树!先取一个点作为顶点,然后左边依次可以取0至N-1个相对应的,右边是N-1到0个,两两配对相乘,就是h(0)*h(n-1) + h(2)*h(n-2) +...... + h(n-1)h(0)=h(n)) (能构成h(N)个)
在2n位二进制数中填入n个1的方案数为c(2n,n),不填1的其余n位自动填0。从中减去不符合要求(由左而右扫描,0的累计数大于1的累计数)的方案数即为所求。
不符合要求的数的特征是由左而右扫描时,必然在某一奇数位2m+1位上首先出现m+1个0的累计数和m个1的累计数,此后的2(n-m)-1位上有n-m个 1和n-m-1个0。如若把后面这2(n-m)-1位上的0和1互换,使之成为n-m个0和n-m-1个1,结果得1个由n+1个0和n-1个1组成的2n位数,即一个不合要求的数对应于一个由n+1个0和n-1个1组成的排列。
反过来,任何一个由n+1个0和n-1个1组成的2n位二进制数,由于0的个数多2个,2n为偶数,故必在某一个奇数位上出现0的累计数超过1的累计数。同样在后面部分0和1互换,使之成为由n个0和n个1组成的2n位数,即n+1个0和n-1个1组成的2n位数必对应一个不符合要求的数。
因而不合要求的2n位数与n+1个0,n-1个1组成的排列一一对应。
显然,不符合要求的方案数为c(2n,n+1)。由此得出输出序列的总数目=c(2n,n)-c(2n,n+1)=1/(n+1)*c(2n,n)。
(这个公式的下标是从h(0)=1开始的)
http://blog.csdn.net/hackbuteer1/article/details/7450250