线性表——链表、队列、堆栈

主要内容

 链表

       链表相加/链表部分翻转/链表去重

       链表划分/链表公共结点

 队列

        拓扑排序

        最短路径条数

 堆栈

        括号是否匹配

        最长括号匹配

        计算逆波兰表达式

        出栈入栈问题


 链表

  链表相加
  

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

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

猜你喜欢

转载自blog.csdn.net/qq_34863439/article/details/83507500
今日推荐