算法基础-- > 链表,堆栈,队列

本文深入讲解了链表、堆栈及队列等经典数据结构的基本原理及其应用案例,包括链表相加、链表部分翻转、链表划分、排序链表中去重等链表操作,以及括号匹配问题、最长括号匹配问题、逆波兰表达式计算等堆栈应用,还介绍了队列在最短路径条数问题和拓扑排序中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

从这篇博文开始,我将总结一些常用的传统算法的思想核心。本篇博文主要总结链表,堆栈,队列。

链表

链表相加

给定两个链表,分别表示两个非负整数。它们的数字逆序存储 在链表中,且每个结点只存储一个数字,计算两个数的和,并且返回和的链表头指针。

如:输入: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次时:

  1. 第m-1个结点pPre,因为总是在它后面进行插入。
  2. 对于第m+i个结点pCur,将pCur结点插入到pPre结点后。
  3. 对于刚开始的第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为右括号,考察它能否与缓冲区中的左括号匹配。

这个匹配过程,是检查缓冲区最后出现的同类型左括号。
即:后进先出——栈

算法步骤:

从前向后扫描字符串:

  1. 遇到左括号x,就压栈x,也即栈中只存左括号;
  2. 遇到右括号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*+

  1. 若当前字符是操作数,则压栈
  2. 若当前字符是操作符,则弹出栈中的两个操作数,计算后仍然压入栈中
  3. 若某次操作,栈内无法弹出两个操作数,则表达式有误。

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:

  1. 结点步数step[0…N-1]初始化为0。
    到每一个结点需要走的步数。

  2. 路径数目pathNum[0…N-1]初始化为0。
    到每一个结点可能会有多少种走法。

  3. 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

拓扑排序的方法:

  1. 从有向图中选择一个没有前驱(即入度为0)的顶点并且输出它;
  2. 从网中删去该顶点(出队列对头结点),并且删去从该顶点发出的全部有向边(与之相连的结点入度减一);
  3. 重复上述两步,直到剩余的网中不再存在没有前趋的顶点为止。

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;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值