从这篇博文开始,我将总结一些常用的传统算法的思想核心。本篇博文主要总结链表,堆栈,队列。
链表
链表相加
给定两个链表,分别表示两个非负整数。它们的数字逆序存储 在链表中,且每个结点只存储一个数字,计算两个数的和,并且返回和的链表头指针。
如:输入:2→4→3、5→6→4,输出:7→0→8
问题分析:因为两个数都是逆序存储,正好可以从头向后依次相加,完成“两个数的竖式计算”。
C ++
#include<stdio.h>
#include <stdlib.h>
using namespace std;
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;
SNode* p1 = pHead1->pNext;
SNode* p2 = pHead2->pNext;
SNode* pCur;
int carray = 0;
int value;
while (p1&& p2)
{
value = p1->value + p2->value + carray;
carray = value / 10;
value %= 10;
pCur = new SNode(value);
pTail->pNext = pCur;
pTail = pCur;
p1 = p1->pNext;
p2 = p2->pNext;
}
//处理较长的连
SNode* p = p1 ? p1 : p2;
while (p)
{
value = p->value + carray;
carray = value / 10;
value %= 10;
pCur = new SNode(value);
pTail->pNext = pCur;
pTail = pCur;
p = p->pNext;
}
//处理可能存在的进位
if (carray != 0)
pTail->pNext = new SNode(carray);
return pSum;
}
void Print(SNode* pHead)
{
pHead = pHead->pNext;
printf("%d", pHead->value);
pHead = pHead->pNext;
while (pHead->pNext)
{
printf("->%d",pHead->value);
pHead = pHead->pNext;
}
printf("\r\n");
}
void Destroy(SNode* p)
{
SNode* next;
while (p)
{
next = p->pNext;
delete p;
p = next;
}
}
int main()
{
SNode* pHead1 = new SNode(0);
int i;
for (i = 0; i < 6; i++)
{
SNode* p = new SNode(rand() % 10);
p->pNext = pHead1->pNext;//注意链表有个数值为0的头结点
pHead1->pNext = p;
}
SNode* pHead2 = new SNode(0);
for (i = 0; i < 9; i++)
{
SNode* p = new SNode(rand() % 10);
p->pNext = pHead2->pNext;
pHead2->pNext = p;
}
Print(pHead1);
Print(pHead2);
SNode* pSum = Add(pHead1, pHead2);
Print(pSum);
Destroy(pHead1);
Destroy(pHead2);
Destroy(pSum);
return 0;
}
JAVA
public class NodeAdd{
public Node add(Node pHead1, Node pHead2){
Node root = new Node(0);
Node pCur = root;
Node pCur1 = pHead1.next;
Node pCur2 = pHead2.next;
int plus = 0;
int value = 0;
while(pCur1 != null && pCur2 != null){
Node p;
value = (pCur1.value + pCur2.value + plus) % 10;
plus = (pCur1.value + pCur2.value + plus) / 10;
p = new Node(value);
pCur.next = p;
pCur1 = pCur1.next;
pCur2 = pCur2.next;
pCur = pCur.next;
}
Node pCur3 = pCur1 != null? pCur1: pCur2;
while(pCur3 != null){
Node p;
value = (pCur3.value + plus) % 10 ;
plus = (pCur3.value + plus) / 10;
p = new Node(value);
pCur.next = p;
pCur3 = pCur3.next;
pCur = pCur.next;
}
if(plus != 0){
pCur.next = new Node(plus);
pCur = pCur.next;
}
pCur.next = null;
return root.next;
}
public static void main(String[] args){
Node pHead1 = new Node(0);
Node p1_1 = new Node(1);
Node p1_2 = new Node(3);
Node p1_3 = new Node(5);
pHead1.next = p1_1;
p1_1.next = p1_2;
p1_2.next = p1_3;
p1_3.next = null;
Node pHead2 = new Node(0);
Node p2_1 = new Node(2);
Node p2_2 = new Node(4);
Node p2_3 = new Node(6);
pHead2.next = p2_1;
p2_1.next = p2_2;
p2_2.next = p2_3;
p2_3.next = null;
NodeAdd obj = new NodeAdd();
Node res = obj.add(pHead1, pHead2);
while(res != null){
System.out.print(res.value+",");
res = res.next;
}
}
}
链表部分翻转
给定一个链表,翻转该链表从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中即可。即头插法。
我们以:给定1→2→3→4→5,m=2,n=4,返回 1→4→3→2→5。为例,画图显示每一步的转换结果。
这里面思想并不是很难,难就难在coding时一定要注意每次在头插法时,其指针指向要正确改变。
由上图我们可知在coding时,需要标记以下几个结点:
在遍历n-m个结点时,在第i次时:
- 第m-1个结点pPre,因为总是在它后面进行插入。
- 对于第m+i个结点pCur,将pCur结点插入到pPre结点后。
- 对于刚开始的第m个结点pFst,因为在每次插入后,pFst结点都要和pCur结点的后一个结点相连。
C++
#include<stdio.h>
#include <stdlib.h>
using namespace std;
typedef struct tagSNode
{
int value;
tagSNode* pNext;
tagSNode(int v) :value(v), pNext(NULL){};
}SNode;
void Print(SNode* pHead)
{
pHead = pHead->pNext;
printf("%d", pHead->value);
pHead = pHead->pNext;
while (pHead->pNext)
{
printf("->%d", pHead->value);
pHead = pHead->pNext;
}
printf("\r\n");
}
void Destroy(SNode* p)
{
SNode* next;
while (p)
{
next = p->pNext;
delete p;
p = next;
}
}
void Reverse(SNode* pHead, int from, int to)
{
SNode* pCur = pHead->pNext;
int i;
SNode* pPre = pHead;
for (i = 0; i < from - 1; i++)
{
pPre = pCur;
pCur = pCur->pNext;
}
SNode* pFst = pCur;
pCur = pCur->pNext;
SNode* pNext;
to--;
for (; i < to; i++)
{
pNext = pCur->pNext;
pCur->pNext = pPre->pNext;
pPre->pNext = pCur;
pFst->pNext = pNext;
pCur = pNext;
}
}
int main()
{
SNode* pHead = new SNode(0);
int i = 0;
for (i = 0; i < 10; i++)
{
SNode*p = new SNode(rand() % 100);
p->pNext = pHead->pNext;
pHead->pNext = p;
}
Print(pHead);
Reverse(pHead, 4, 8);
Print(pHead);
Destroy(pHead);
return 0;
}
JAVA
public class NodeOverTurn{
public Node overTurn(Node pHead, int m, int n){
if(pHead == null)
return pHead;
Node pCur = pHead;
for(int i = 1; i < m; i++){
if(pCur == null)
return pHead;
pCur = pCur.next;
}
Node pPre = pCur;
Node pCur2 = pPre.next;
if(pCur2 == null)
return pHead;
for(int i = m; i < n; i++){
Node pNext = pCur2.next;
if(pNext == null)
return pHead;
pCur2.next = pNext.next;
pNext.next = pPre.next;
pPre.next = pNext;
}
return pHead;
}
public static void main(String[] args){
NodeOverTurn obj = new NodeOverTurn();
Node pHead1 = new Node(0);
Node p1_1 = new Node(1);
Node p1_2 = new Node(2);
Node p1_3 = new Node(3);
Node p1_4 = new Node(4);
Node p1_5 = new Node(5);
Node p1_6 = new Node(6);
pHead1.next = p1_1;
p1_1.next = p1_2;
p1_2.next = p1_3;
p1_3.next = p1_4;
p1_4.next = p1_5;
p1_5.next = p1_6;
p1_6.next = null;
Node pCur = pHead1.next;
while(pCur != null){
System.out.print(pCur.value+",");
pCur = pCur.next;
}
System.out.println();
Node res = obj.overTurn(pHead1, 10, 10);
res = res.next;
while(res != null){
System.out.print(res.value+",");
res = res.next;
}
}
}
链表划分
给定一个链表和一个值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的末端即可。
C++
#include<stdio.h>
#include <stdlib.h>
using namespace std;
typedef struct tagSNode
{
int value;
tagSNode* pNext;
tagSNode(int v) :value(v), pNext(NULL){};
}SNode;
void Print(SNode* pHead)
{
pHead = pHead->pNext;
printf("%d", pHead->value);
pHead = pHead->pNext;
while (pHead->pNext)
{
printf("->%d", pHead->value);
pHead = pHead->pNext;
}
printf("\r\n");
}
void Destroy(SNode* p)
{
SNode* next;
while (p)
{
next = p->pNext;
delete p;
p = next;
}
}
void Partition(SNode* pHead, int pivotKey)
{
SNode* LeftHead = new SNode(0);
SNode* RightHead = new SNode(0);
SNode* pCur = pHead->pNext;
SNode* LeftTail = LeftHead;
SNode* RightTail = RightHead;
while (pCur)
{
if (pCur->value <= pivotKey)
{
LeftTail->pNext = pCur;
LeftTail = LeftTail->pNext;
}
else
{
RightTail->pNext = pCur;
RightTail = RightTail->pNext;
}
pCur = pCur->pNext;
}
pHead->pNext = LeftHead->pNext;
LeftTail->pNext = RightHead->pNext;
RightTail->pNext = NULL;//这一句很重要,如果没有这句代码,在输出链表时会源源不断的输出。
delete LeftHead;
delete RightHead;
}
int main()
{
SNode* pHead = new SNode(0);
int i = 0;
for (i = 0; i < 10; i++)
{
SNode*p = new SNode(rand() % 100);
p->pNext = pHead->pNext;
pHead->pNext = p;
}
Print(pHead);
Partition(pHead, 50);
Print(pHead);
Destroy(pHead);
return 0;
}
JAVA
public class NodeSortBySize{
public Node func(Node pHead, int key){
if(pHead == null)
return null;
Node pHead1 = new Node(-1);
Node pCur1 = pHead1;
Node pHead2 = new Node(-1);
Node pCur2 = pHead2;
Node pCur = pHead.next;
while(pCur != null){
if(pCur.value <= key){
pCur1.next = pCur;
pCur1 = pCur1.next;
}else{
pCur2.next = pCur;
pCur2 = pCur2.next;
}
pCur = pCur.next;
}
pCur1.next = pHead2.next;
return pHead1.next;
}
public static void main(String[] args){
NodeSortBySize obj = new NodeSortBySize();
Node pHead1 = new Node(0);
Node p1_1 = new Node(1);
Node p1_2 = new Node(4);
Node p1_3 = new Node(2);
Node p1_4 = new Node(6);
Node p1_5 = new Node(3);
Node p1_6 = new Node(5);
pHead1.next = p1_1;
p1_1.next = p1_2;
p1_2.next = p1_3;
p1_3.next = p1_4;
p1_4.next = p1_5;
p1_5.next = p1_6;
p1_6.next = null;
Node pCur = pHead1.next;
while(pCur != null){
System.out.print(pCur.value+",");
pCur = pCur.next;
}
System.out.println();
Node res = obj.func(pHead1, 1);
while(res != null){
System.out.print(res.value+",");
res = res.next;
}
}
}
时间复杂度是O(N),空间复杂度为O(1)
排序链表中去重
给定排序的链表,删除重复元素,只保留重复元素第一次出现的结点。
给定: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;重复上述过程,直至链表尾端。
#include<stdio.h>
#include <stdlib.h>
using namespace std;
typedef struct tagSNode
{
int value;
tagSNode* pNext;
tagSNode(int v) :value(v), pNext(NULL){};
}SNode;
void Print(SNode* pHead)
{
pHead = pHead->pNext;
printf("%d", pHead->value);
pHead = pHead->pNext;
while (pHead->pNext)
{
printf("->%d", pHead->value);
pHead = pHead->pNext;
}
printf("\r\n");
}
void Destroy(SNode* p)
{
SNode* next;
while (p)
{
next = p->pNext;
delete p;
p = next;
}
}
void DeleteDuplicateNode(SNode* pHead)
{
SNode* pPre = pHead->pNext;
SNode* pCur;
while (pPre)
{
pCur = pPre->pNext;
if (pCur && (pCur->value == pPre->value))
{
pPre->pNext = pCur->pNext;
delete pCur;
}
else
{
pPre = pCur;
}
}
}
int main()
{
SNode* pHead = new SNode(0);
int data[] = { 2, 3, 3, 5, 7, 8, 8, 8, 9, 9, 30 };
int size = sizeof(data) / sizeof(int); //当操作数具有数组类型时,其结果是数组的总字节数,而sizeof(int)=4
for (int i = size-1; i >= 0; i--)
{
SNode*p = new SNode(data[i]);
p->pNext = pHead->pNext;
pHead->pNext = p;
}
Print(pHead);
DeleteDuplicateNode3(pHead);
Print(pHead);
Destroy(pHead);
return 0;
}
解法二:
void DeleteDuplicateNode2(SNode* pHead)
{
SNode* pPre = pHead;
SNode* pCur = pPre->pNext;
SNode* pNext;
while (pCur)
{
pNext = pCur->pNext;
while (pNext&&(pCur->value==pNext->value))
{
pPre->pNext = pNext;
delete pCur;
pCur = pNext;
pNext = pCur->pNext;
}
pPre = pCur;
pCur = pNext;
}
}
咱们来比较下上面两种链表去重的解法,解法一很自然的在链表选取连续 两个结点,从左到右不停的滑动,每次滑动都会比较这连续两个结点的value值是否相等,如果相等则去掉后面一个结点。修改相关指针继续滑动;解法二中是从链表中选取三个结点,其中第二第三个结点相当于解法一中的两个结点,不断的滑动比较,第一分结点始终在这两个结点的前面一个结点。
相比较而言,解法二扩展性更好。
若题目变成:若发现重复元素,则重复元素全部删除,代码应该怎么实现呢?
给定:2→3→3→5→7→8→8→8→9→9→10
返回:2→5→7→10
对于这个变种题,上面的解法二稍微改下就可以解决这道题。因为解法二中标记了重复结点之前的一个结点。
void DeleteDuplicateNode3(SNode* pHead)
{
SNode* pPre = pHead;
SNode* pCur = pPre->pNext;
SNode* pNext;
bool bDup;
while (pCur)
{
pNext = pCur->pNext;
bDup = false;
while (pNext&&(pCur->value==pNext->value))
{
pPre->pNext = pNext;
delete pCur;
pCur = pNext;
pNext = pCur->pNext;
bDup = true;
}
if (bDup)//如果此时pCur与原数据重复,删之
{
pPre->pNext = pNext;
delete pCur;
}
else //pCur未发现重复,则pPre后移
{
pPre = pCur;
}
pCur = pNext;
}
}
Java
public class NodeDroplicate{
public Node dropDuplicate(Node pHead){
Node pPre = pHead;
Node pCur = pHead.next;
while(pCur != null && pCur.next != null){
if(pCur.value == pCur.next.value){
pPre.next = pCur.next;
pCur = pCur.next;
}else{
pPre = pPre.next;
pCur = pCur.next;
}
}
return pHead;
}
public Node dropDuplicate2(Node pHead){ // 重复节点全部删除
Node pPre = pHead;
Node pCur = pPre.next;
while(pCur != null){
boolean bDup = false;
while(pCur.next != null && pCur.value == pCur.next.value){
pPre.next = pCur.next;
pCur = pCur.next;
bDup = true;
}
if(bDup == true){
pPre.next = pCur.next;
}
else{
pPre = pCur;
}
pCur = pCur.next;
}
return pHead;
}
public static void main(String[] args){
Node pHead1 = new Node(0);
Node p1_1 = new Node(4);
Node p1_2 = new Node(2);
Node p1_3 = new Node(2);
Node p1_4 = new Node(2);
Node p1_5 = new Node(2);
Node p1_6 = new Node(2);
pHead1.next = p1_1;
p1_1.next = p1_2;
p1_2.next = p1_3;
p1_3.next = p1_4;
p1_4.next = p1_5;
p1_5.next = p1_6;
p1_6.next = null;
NodeDroplicate obj = new NodeDroplicate();
Node res = obj.dropDuplicate2(pHead1);
Node pCur = res.next;
while(pCur != null){
System.out.print(pCur.value+",");
pCur = pCur.next;
}
}
}
链表总结:可以发现,纯链表的题目,往往不难,但需要需要扎实的Coding基本功,在实现过程中,要特别小心next的指向,此外,删除结点时,一定要确保该结点不再需要。
stack
堆栈是一种特殊的线性表,只允许在表的顶端top进行插入或者删除操作,是一种操作受限制的线性表。
栈元素服从后进先出原则: LIFO——Last In First Out
括号匹配问题
给定字符串,仅由"()[]{}"六个字符组成。设计算法,判断该字符串是否有效。括号必须以正确的顺序配对,如:“()”、“()[]”是有效的,但“([)]”无效。
括号匹配问题是堆栈里面一个经典应用。
算法分析:
在考察第i位字符c与前面的括号是否匹配时:
- 如果c为左括号,开辟缓冲区记录下来,希望c能够与后面出现的同类型最近右括号匹配。
- 如果c为右括号,考察它能否与缓冲区中的左括号匹配。
这个匹配过程,是检查缓冲区最后出现的同类型左括号。
即:后进先出——栈
算法步骤:
从前向后扫描字符串:
- 遇到左括号x,就压栈x,也即栈中只存左括号;
- 遇到右括号y:如果发现栈顶元素x和该括号y匹配,则栈顶元素出栈。
如果扫描到一个右括号则:
- 如果栈顶元素x和该右括号y不匹配,则返回结果字符串不匹配,退出程序;
- 如果栈为空,则返回结果字符串不匹配,退出程序;
- 扫描完成后,如果栈恰好为空,则字符串匹配,否则,字符串不匹配,退出程序。
C++
#include<stdio.h>
#include <stdlib.h>
#include<stack>
using namespace std;
bool isLeft(char c)
{
return c == '(' || c == '[' || c == '{';
}
bool isMatch(char c, char d)
{
if (c == '(')
return d == ')';
else if (c == '[')
return d == ']';
else if (c == '{')
return d == '}';
else
{
return false;
}
}
bool Match(const char* p)
{
stack<char> s;
char cur;
while (*p)
{
cur = *p;
if (isLeft(cur))
{
s.push(cur);
}
else
{
if (s.empty() || !isMatch(s.top(), cur))
{
return false;//return既有返回结果功能也有退出程序作用。
}
s.pop();//匹配的话弹出栈顶元素。
}
p++;
}
return s.empty();
}
int main()
{
char *p = "(([])[]))[()]";
bool match = Match(p);
if (match)
printf("匹配!\n");
else
{
printf("不匹配!\n");
}
}
JAVA
import java.util.*;
public class Symbol_match{
Stack<Character> stack = new Stack<>();
public Boolean func(char[] arr){
if(arr.length == 0)
return true;
for(int i = 0; i < arr.length; i++){
char tmp = arr[i];
if(tmp == '[' || tmp == '{' || tmp == '(')
stack.push(tmp);
else if((tmp == ']' || tmp == '}' || tmp == ')') && stack.isEmpty())
return false;
else if(tmp == ']'){
if(!stack.isEmpty() && stack.peek() != '[')
return false;
if(!stack.isEmpty() && stack.peek() == '[')
stack.pop();
}
else if(tmp == '}'){
if(!stack.isEmpty() && stack.peek() != '{')
return false;
if(!stack.isEmpty() && stack.peek() == '{')
stack.pop();
}
else if(tmp == ')'){
if(!stack.isEmpty() && stack.peek() != '(')
return false;
if(!stack.isEmpty() && stack.peek() == '(')
stack.pop();
}
}
if(stack.isEmpty())
return true;
return false;
}
public static void main(String[] args){
String str = "[()]{[]}";
Symbol_match obj = new Symbol_match();
boolean res = obj.func(str.toCharArray());
System.out.println(res);
}
}
最长括号匹配问题
给定字符串,仅包含左括号‘(’和右括号‘)’,它可能不是括号匹配的,设计算法,找出最长匹配的括号子串,返回该子串的长度。
如:
(():2
()():4
()(()):6
(()()):6
这里需要注意:是找找出最长匹配的括号子串,子串是字符串内某一段连续 的子字符串。所以这个匹配的字符串必须是连续,不能说前面有一对匹配,隔了几个字符后面又有一对或几对匹配,不能把这几个不连续的匹配求和作为结果。例如“()((())”最长匹配子串长度为4而不是6。
算法分析:
记起始匹配位置start=-1;最大匹配长度ml=0:
考察第 i(i从0开始)位字符c:
如果c为左括号,压栈;
如果c为右括号,它一定与栈顶左括号匹配;
- 如果栈为空,表示没有匹配的左括号,start=i,为下一次可能的匹配做准备。
- 如果栈不空,出栈(因为和c匹配了);
如果栈为空,i-start即为当前找到的匹配长度,检查i-start是否比ml更大,使得ml得以更新;
如果栈不空,则当前栈顶元素t是上次匹配的最后位置,检查i-t是否比ml更大,使得ml得以更新。
注:因为入栈的一定是左括号,显然没有必要将它们本身入栈,应该入栈的是该字符在字符串中的索引。
C++
#include<stdio.h>
#include <stdlib.h>
#include<stack>
using namespace std;
int getLongestMatch(char* p)
{
int n = (int)strlen(p);
int start = -1;
int ml = 0;
stack<int> s;
for (int i = 0; i < n; i++)
{
if (p[i] == '(')
{
s.push(i);
}
else
{
if (s.empty())
{
start = i;
}
else
{
s.pop();
if (s.empty())
{
ml = ml>i - start ? ml : i - start;
}
else
{
ml = ml > i - s.top() ? ml : i - s.top();
}
}
}
}
return ml;
}
JAVA
import java.util.*;
public class GetLongestMatch{
public int func(String str){
int start = -1;
Stack<Integer> stack = new Stack<>();
int ml = 0;
for(int i=0; i < str.length(); i++){
char tmp = str.charAt(i);
if(tmp == '(')
stack.push(i);
else if(tmp == ')'){
if(!stack.isEmpty() && str.charAt(stack.peek()) == '('){
int j = stack.pop();
int len = 0;
if(stack.isEmpty())
len = i - start;
else
len = i - j + 1 ;
if(len > ml) ml = len;
}
else if((!stack.isEmpty() && str.charAt(stack.peek()) != '(') || stack.isEmpty()){
start = i;
}
}
}
return ml;
}
public static void main(String[] args){
GetLongestMatch obj = new GetLongestMatch();
int res = obj.func(")(())");
System.out.println(res);
}
}
堆栈和逆波兰表达式RPN
逆波兰表达式Reverse Polish Notation,又叫后缀表达式。
习惯上,二元运算符总是置于与之相关的两个运算对象之间,即中缀表达方法。波兰逻辑学家J.Lukasiewicz于1929年提出了运算符都置于其运算对象之后,故称为后缀表示。
如:
- 中缀表达式:a+(b-c)*d
- 后缀表达式:abc-d*+
从上面也可以看出:逆波兰表达式不需要带括号,自适应了优先级。
事实上,二元运算的前提下,中缀表达式可以对应一颗二叉树;逆波兰表达式即该二叉树后序遍历的结果。
计算给定的逆波兰表达式的值。有效操作只有±*/,每个操作数都是整数。
如:
- “2”, “1”, “+”, “3”, “*”:9——(2+1)*3
- “4”, “13”, “5”, “/”, “+”:6——4+(13/5)
算法分析:
例如:abc-d*+
- 若当前字符是操作数,则压栈
- 若当前字符是操作符,则弹出栈中的两个操作数,计算后仍然压入栈中
- 若某次操作,栈内无法弹出两个操作数,则表达式有误。
C++
#include<stdio.h>
#include <stdlib.h>
#include<stack>
#include<string>
using namespace std;
bool istoken(const char* c)
{
return (c[0] == '+' || c[0] == '-' || c[0] == '*' || c[0] == '/');
}
int RPN(const char* p[],int n)
{
stack<int> s;
int a, b;
const char *c;
for (int i = 0; i < n; i++)
{
c = p[i];
if (!istoken(c))
{
s.push(atoi(c));//字符串转int数字
}
else
{
a = s.top();
s.pop();
b = s.top();
s.pop();
if (c[0] == '+')
s.push(a + b);
else if (c[0] == '-')
s.push(a - b);
else if (c[0] == '/')
s.push(a / b);
else if (c[0] == '*')
{
s.push(a * b);
}
}
}
return s.top();
}
int main()
{
const char* p[] = { "4", "13", "5", "/", "+" };//因为有两位数的数字,那么必须这样定义
int n = sizeof(p) / sizeof(const char*);
int ml = RPN(p,n);
printf("%d ", ml);
}
JAVA
import java.util.Stack;
public class RPN{
public boolean isSyb(String s){
if(s == "+" || s == "-" || s == "/" || s == "*")
return true;
return false;
}
public int func(String[] str){
Stack<String> stack = new Stack<>();
for(int i = 0; i < str.length; i++){
String tmp = str[i];
if(isSyb(tmp)){
int a = Integer.valueOf(stack.pop());
int b = Integer.valueOf(stack.pop());
int c;
switch(tmp){
case "+":
c = a + b;
break;
case "-":
c = b - a;
break;
case "*":
c = a * b;
break;
default:
c = b/a;
break;
}
stack.push(String.valueOf(c));
}
else{
stack.push(tmp);
}
}
int res = Integer.valueOf(stack.pop());
return res;
}
public static void main(String[] args){
RPN obj = new RPN();
String[] str = {"4", "13", "5", "/", "+"};
int res = obj.func(str);
System.out.println(res);
}
}
queue(队列)
队列是一种特殊的线性表,只允许在表的前端front进行删除操作,在表的后端rear进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。
队列元素服从先进先出原则: FIFO——First In First Out
队列与广度优先遍历:最短路径条数问题
给定如图所示的无向连通图,假定图中所有边的权值都为1,显然,从源点A到终点T的最短路径有多条,求不同的最短路径的数目。
通常涉及到最短路径问题,往往是一种广度优先搜索的应用。
算法分析:
权值相同的最短路径问题,则单源点Dijkstra算法退化成BFS广度优先搜索,假定起点为0,终点为N:
-
结点步数step[0…N-1]初始化为0。
到每一个结点需要走的步数。 -
路径数目pathNum[0…N-1]初始化为0。
到每一个结点可能会有多少种走法。 -
pathNum[0] = 1。
最开始的结点走法即自己走到自己为1。
若从当前结点i扩展到邻接点 j 时,也即是结点 i 和结点 j 连通 情况下:
-
若step[ j ]为0,则
step[ j ]=step[ i ]+1,pathN[ j ] = pathN[ i ] -
若step[ j ]==step[ i ]+1,则
pathN[ j ] += pathN[ i ]
可考虑一旦扩展到结点N,则提前终止算法。
c++
#include<stdio.h>
#include <stdlib.h>
#include<queue>
using namespace std;
const int N = 16;
int Calc(int G[N][N])
{
int step[N];//每个结点第几步到达
int pathNumber[N];//到每个结点有几种走法
memset(step, 0, sizeof(int)*N);
memset(pathNumber, 0, sizeof(int)*N);
pathNumber[0] = 1;
queue<int> q;//当前搜索的结点,这里队列的作用就是从起点开始,然后不断寻找与其相连的结点,依次的循环,
//依次的将这些结点push到队列中。然后又按照先进先出顺序不断的出队列。
q.push(0);//按照上图例子中,起点序号为0,终点序号15,故首先将起点结点加入队列,然后依次找相连结点。
int from, i, s;
while (!q.empty())
{
from = q.front();
q.pop();
s = step[from] + 1;
for (i = 1; i < N; i++)//0是起点不遍历,每次弹出队列头部元素,与其他所有元素判断是否连通,这是典型的广度遍历。
{
if (G[from][i] = 1)//连通
{
//i尚未可达或发现更快的路
if (step[i] == 0 || step[i] > s)
{
step[i] = s;
pathNumber[i] = pathNumber[from];
q.push(i);//因为第 i 个结点step=0,它应该没在队列中存在过,所以这里把它放进队列中。
}
else if (step[i] == s)
{
pathNumber[i] += pathNumber[from];
}
}
}
}
return pathNumber[N - 1];
}
JAVA
import java.util.Queue;
import java.util.LinkedList;
public class Nosp{
public int func(int[][] G){ // 行数和列数一样的二维数组
Queue<Integer> queue = new LinkedList<Integer>();
int N = G.length;
int[] steps = new int[N]; // 到每个点的最短步数
int[] pathNum = new int[N]; // 到每个点的最短步数的路径数
for(int i = 0; i < N; i++){
steps[i] = 0;
pathNum[i] = 0;
}
step[0] = 0;
pathNum[0] = 1;
queue.offer(0);
while(!queue.isEmpty()){
int a = queue.poll();
s = steps[a] + 1;
for(int i = 0; i< N; i++){
if(i == a)
continue;
if(G[a][i] == 1){
if(step[i] == 0 || steps[i] > s){ // 当与之相邻的点步长为0(说明还没遍历过) 或者大于s(表示当前步长非最短)
steps[i] = s;
pathNum[i] = pathNum[a];
queue.offer(i);
}else if(steps[i] == s){
pathNum[i] += pathNum[a];
}
}
}
}
return pathNum[N-1];
}
}
队列与拓扑排序
对一个有向无环图(Directed Acyclic Graph,DAG)G进行拓扑排序,是将G中所有顶点排成线性序列,使得图中任意一对顶点u和v,若边(u,v)∈E(G),则u在线性序列中出现在v之前。
一种可能的拓扑排序结果2->8->0->3->7->1->5->6->9->4->11->10->12
拓扑排序的方法:
- 从有向图中选择一个没有前驱(即入度为0)的顶点并且输出它;
- 从网中删去该顶点(出队列对头结点),并且删去从该顶点发出的全部有向边(与之相连的结点入度减一);
- 重复上述两步,直到剩余的网中不再存在没有前趋的顶点为止。
C++
#include<stdio.h>
#include <stdlib.h>
#include<queue>
using namespace std;
//结点数为n,用邻接矩阵graph[n][n]存储边权
//用indegree[n]存储每个结点入度
const int n = 16;
void topologic(int* toposort, int indegree[n], int graph[n][n])
{
int cnt = 0;
queue<int> q;
for (int i = 0; i < n; i++)
{
if (!indegree[i])
q.push(i);//队列首先存储那些入度为0的结点。
}
int cur;
while (!q.empty())
{
cur = q.front();//出队列获得头部第一个结点。
q.pop();
toposort[cnt++] = cur;//按出队列的顺序存进数组,因为是先进先出,所以先存的是有向边头头结点,自然先出的也是有向边头结点。这就保证拓扑排序存储顺序。
for (int i = 0; i < n; i++)
{
if (graph[cur][i])//每次出队列头结点,然后把这个头结点与其他所有结点进行判断是否连通,典型的广度遍历。
{
indegree[i]--;//如果连通,则i这个结点的入度减一
if (toposort[i] == 0)
q.push(i);
}
}
}
}
JAVA
import java.util.Queue;
import java.util.LinkedList;
public class Topologic{
public int[] func(int[][] G, int[] indegree){
Queue<Integer> queue = new LinkedList<>();
int N = G.length;
int[] res = new int[N];
for(int i = 0; i < N; i++)
if(indegree[i] == 0)
queue.offer(i);
int cnt = 0;
while(!queue.isEmpty()){
int a = queue.poll;
res[cnt] = a;
for(int i = 0; i < N; i++){
if(i == a)
continue;
if(G[a][i] == 1){
indegree[i] -= 1;
G[a][i] = 0;
if(res[i] == 0)
queue.offer(i);
}
}
}
return res;
}
}