主要内容
链表
链表相加/链表部分翻转/链表去重
链表划分/链表公共结点
队列
拓扑排序
最短路径条数
堆栈
括号是否匹配
最长括号匹配
计算逆波兰表达式
出栈入栈问题
链表
链表相加
给定两个链表,分别表示两个非负整数。它 们的数字逆序存储在链表中,且每个结点只 存储一个数字,计算两个数的和,并且返回 和的链表头指针。
如:输入:2→4→3、5→6→4,输出:7→0→8
问题分析
输入: 2→4→3、5→6→4输出:7→0→8
因为两个数都是逆序存储,正好可以从头向 后依次相加,完成“两个数的竖式计算”。
注意考虑两个数的位数不相同的情况。
typedef struct tagSNode
{
int value;
tagSNode* pNext;
tagSNode(int v):value(v),pNext(NULL){};
}SNode;
SNode*add(SNode*pHead1,SNode*pHead2)
{
SNode* pSum=new SNode(0);
SNode* pTail=pSum; //新结点插入到pTail的后面
SNode*p1=pHead1->pNext;
SNode*p2=pHead2->pNext;
SNode*pCur;
int carry=0; //进位
int value=0;
while(p1&&p2)
{
value=(p1->value+p2->value+carry)%10;
carry=(p1->value+p2->value+carry)/10;
pCur=new SNode(value);
pTail->pNext=pCur; //新结点链接到pTail的后面
pTail=pCur;
p1=p1->pNext; //处理下一位
p2=p2->pNext;
}
//处理较长的链
SNode*p=p1? p1:p2;
while(p)
{
value=(p->value+carry)%10;
carry=(p->value+carry)/10;
pCur=new SNode(value);
pTail->pNext=pCur;
pTail=pCur;
p=p->pNext;
}
//处理可能存在的进位
if(carry)
{
pTail->pNext=new SNode(carry);
}
return pSum;
}
链表的部分翻转
给定一个链表,翻转该链表从m到n的位置。 要求直接翻转而非申请新空间。
如:给定1→2→3→4→5,m=2,n=4,返回 1→4→3→2→5。
假定给出的参数满足:1≤m≤n≤链表长度。
分析
空转m-1次,找到第m-1个结点,即开始翻转 的第一个结点的前驱,记做head; 以head为起始结点遍历n-m次,将第i次时, 将找到的结点插入到head的next中即可。
即头插法
typedef struct tagSNode
{
int value;
tagSNode* pNext;
tagSNode(int v):value(v),pNext(NULL){};
}SNode;
void reverse(SNode*pHead,int from,int to)
{
SNode* pCur=pHead->pNext;
for (int i = 0; i < from-1; ++i)
{
pHead=pCur;
pCur=pCur->pNext;
}
SNode*pPre=pCur;
pCur=pCur->pNext;
to--;
SNode*pNext;
for (int i = 0; i < to; ++i)
{
pNext=pCur->pNext;
pCur->pNext=pHead->pNext;
pHead->pNext=pCur;
pPre->pNext=pNext;
pCur=pNext;
}
}
排序链表中去重
给定排序的链表,删除重复元素,只保留重 复元素第一次出现的结点。
如: 给定:2→3→3→5→7→8→8→8→9→9→10
返回:2→3→5→7→8→9→10
问题分析
若p->next的值和p的值相等,则将p->next>next赋值给p,删除p->next;重复上述过 程,直至链表尾端。
排序链表中去重2
若题目变成:若发现重复元素,则重复元素 全部删除,代码应该怎么实现呢?
如: 给定:2→3→3→5→7→8→8→8→9→9→10
返回:2→5→7→10
链表划分
给定一个链表和一个值x,将链表划分成两 部分,使得划分后小于x的结点在前,大于 等于x的结点在后。在这两部分中要保持原 链表中的出现顺序。
如:给定链表1→4→3→2→5→2和x = 3,返回 1→2→2→4→3→5
问题分析
分别申请两个指针p1和p2,小于x的添加到 p1中,大于等于x的添加到p2中;最后,将 p2链接到p1的末端即可。 时间复杂度是O(N),空间复杂度为O(1);该 问题其实说明:快速排序对于单链表存储结 构仍然适用。
注:不是所有排序都方便使用链表存储,如堆 排序,将不断的查找数组的n/2和n的位置,用链 表做存储结构会不太方便。
单链公共结点问题
给定两个单向链表,计算两个链表的第一个 公共结点,若没有公共节点,返回空。
问题分析
令两链表的长度为m、n,不妨认为m≥n,由 于两个链表从第一个公共结点到链表的尾结 点是完全重合的。所以前面的(m-n)个结点一 定没有公共结点。
算法:先分别遍历两个链表得到它们的长度 m,n。长链表空转|m-n|次,同步遍历两链 表,直到找到相同结点或到链表结束。 时间复杂度为O(m+n)。
队列
拓扑排序
拓扑排序的方法
从有向图中选择一个没有前驱(即入度为0)的 顶点并且输出它; 从网中删去该顶点,并且删去从该顶点发出 的全部有向边;
重复上述两步,直到剩余的网中不再存在没 有前驱的顶点为止
拓扑排序的进一步思考
拓扑排序的本质是不断输出入度为0的点,该算法可 用于判断图中是否存在环; 可以用队列(或者栈)保存入度为0的点,避免每次遍 历所有点;
每次更新连接点的入度即可。
拓扑排序其实是给定了结点的一组偏序关系。
“拓扑”的涵义不限于此,在GIS中,它往往指点、 线、面、体之间的相互邻接关系,即“橡皮泥集 合” 。存储这些关系,往往能够对某些算法带来好 处。
计算不自交的空间曲面是否能够围成三维体 提示:任意三维边都邻接两个三维曲面
最短路径条数
给定如图所示的无向连通图,假定图中所有 边的权值都为1,显然,从源点A到终点T的 最短路径有多条,求不同的最短路径的数目
数据结构的选择
权值相同的最短路径问题,则单源点Dijkstra 算法退化成BFS广度优先搜索,假定起点为 0,终点为N:
结点步数step[0…N-1]初始化为0
路径数目pathNum[0…N-1]初始化为0 pathNum[0] = 1
算法分析
若从当前结点i扩展到邻接点j时: 若step[j]为0,则
step[j]=step[i]+1,pathN[j] = pathN[i]
若step[j]==step[i]+1,则
pathN[j] += pathN[i]
可考虑扩展到结点N,则提前终止算法。
堆栈
括号是否匹配
给定字符串,仅由"()[]{}"六个字符组成。设 计算法,判断该字符串是否有效。
括号必须以正确的顺序配对,如:“()”、“()[]” 是有效的,但“([)]”无效
算法分析
在考察第i位字符c与前面的括号是否匹配时: 如果c为左括号,开辟缓冲区记录下来,希望c能够 与后面出现的同类型最近右括号匹配。
如果c为右括号,考察它能否与缓冲区中的左括号 匹配。
这个匹配过程,是检查缓冲区最后出现的同类型左括号
即:后进先出——栈
括号匹配算法流程
从前向后扫描字符串: 遇到左括号x,就压栈x;
遇到右括号y:
如果发现栈顶元素x和该括号y匹配,则栈顶元素出栈, 继续判断下一个字符。
如果栈顶元素x和该括号y不匹配,字符串不匹配;
如果栈为空,字符串不匹配;
扫描完成后,如果栈恰好为空,则字符串匹配,否 则,字符串不匹配。
最长括号匹配
给定字符串,仅包含左括号‘(’和右括号 ‘)’,它可能不是括号匹配的,设计算法, 找出最长匹配的括号子串,返回该子串的长 度。
如:
(():2
()():4
()(()):6
(()()):6
算法分析
记起始匹配位置start=-1;最大匹配长度ml=0: 考察第i位字符c:
如果c为左括号,压栈;
如果c为右括号,它一定与栈顶左括号匹配;
如果栈为空,表示没有匹配的左括号,start=i,为下一次可能 的匹配做准备
如果栈不空,出栈(因为和c匹配了);
如果栈为空,i-start即为当前找到的匹配长度,检查i-start是否比 ml更大,使得ml得以更新;
如果栈不空,则当前栈顶元素t是上次匹配的最后位置,检查i-t是 否比ml更大,使得ml得以更新。
注:因为入栈的一定是左括号,显然没有必要将它们本身入栈, 应该入栈的是该字符在字符串中的索引。
逆波兰表达式RPN
Reverse Polish Notation,即后缀表达式。
习惯上,二元运算符总是置于与之相关的两 个运算对象之间,即中缀表达方法。波兰逻 辑学家J.Lukasiewicz于1929年提出了运算符 都置于其运算对象之后,故称为后缀表示。
如:
中缀表达式:a+(b-c)*d
后缀表达式:abc-d*+
运算与二叉树
事实上,二元运算的前提下,中缀表达式可 以对应一颗二叉树;逆波兰表达式即该二叉 树后序遍历的结果。
中缀表达式:a+(b-c)*d
后缀表达式:abc-d*+
该结论对多元运算也成立, 如“非运算”等
计算逆波兰表达式
计算给定的逆波兰表达式的值。有效操作只 有+-*/,每个操作数都是整数。
如:
"2", "1", "+", "3", "*":9——(2+1)*3
"4", "13", "5", "/", "+":6——4+(13/5)
逆波兰表达式的计算方法
abc-d*+ 若当前字符是操作数,则压栈
若当前字符是操作符,则弹出栈中的两个操 作数,计算后仍然压入栈中
若某次操作,栈内无法弹出两个操作数,则表 达式有误。
入栈出栈问题
给定无重复元素的两个等长数组,分别表述 入栈序列和出栈序列,请问:这样的出栈序 列是否可行。
如:入栈序列为“ABCDEFG”、出栈序列为 “BAEDFGC”,则可行。
入栈序列“ABCD”、出栈序列“BDAC”,不可 行。
问题分析
使用一个堆栈S来模拟压栈出栈的操作。记入栈序 列为A,出栈序列为B 遍历B的每个元素b:
情形1:若b等于栈顶元素s,恰好匹配,则检查B的 下一个元素,栈顶元素s出栈;
情形2:若b不等于栈顶元素s,则将A的当前元素入 栈,目的是希望在A的剩余元素中找到b。
在情形1中,若栈S为空,则认为b无法与栈内元素匹配, 则调用情形2。