大话数据结构读书笔记三

Posted by Cfeng on March 9, 2019

定义

  • 栈是限定仅在表尾(栈顶top)进行插入和删除操作的线性表

Java中stack就是继承了vector

public E push(E item)//入栈
public synchronized E pop()//出栈
public synchronized E peek()//查看栈顶元素
public boolean empty()//判断是否为空
public synchronized int search(Object o)//查找最靠近栈顶的元素o位置

所以我们看C++里的stack吧

//栈的定义
typedef int SElemType;
typedef struct{
    SElemType data[MAXSIZE];
    int top;
}sqstack;

//入栈
Status push(sqstack *S, SElemType e){
    if(S->top==MAXSIZE){
        return ERROR;//满了
    }
    S->top++;
    S->data[S->top]=e;
    return SUCCESS;
}
//出栈
Status pop(sqstack *S, SElemType e){
    if(s->top==-1){
        return ERROR;//栈空
    }
    *e=s->data[s->top];
    s->top--;
    return SUCCESS;
}
//时间复杂度均为O(1)

两个栈共享空间

使用一个数组,数组的两个端点可以分别做两个栈的栈底。只需要两个值分别记录栈顶的位置即刻,如果两个值相差一即为两个栈都满了。

栈的链式存储结构

把栈顶放在单链表的头部

栈的应用—-递归

递归过程退回的顺序是它前行的逆序。在退回过程中,可能要执行某些动作,包括要恢复在前行过程中存储起来的某些数据。这种存储某些数据,并在后面以存储的逆序恢复这些数据,以提供之后使用的需求。就类似于栈。

在前行阶段,每次函数递归,函数的局部变量、参数值以及返回地址都被压入栈中。在退回阶段,位于栈顶的局部变量、参数值以及返回地址被弹出,用于返回调用层次中执行代码的其余部分,也就是恢复了调用的状态。(由系统代劳管理)

栈的应用—-四则表达式

后缀表达式:所有的符号都是在要运算的数字后面出现。 9+(3-1)*3+10/2的后缀表达式是9 3 1 - 3 * + 10 2 / + 后缀表达式的计算方式就是从左到右🏪,如果遇到数字就入栈,遇到运算符号,就将栈顶的两个元素提取出来进行运算,得到一个新的数字后,将其压入栈中。当表达式遍历完后,将栈中最后的数字提出来就是答案。

中缀表达式转换为后缀表达式

我们平时用的数学表达式叫做中缀表达式。 方法:从左到右遍历中缀表达式中每个字符,遇到数字就输出,即成为后缀表达式的一部分,如果遇到的是符号,则判断其与栈顶元素的符号的优先级,如果低于或等于栈顶元素,就栈中元素依次出栈,直到栈顶元素优先级比新元素低,如果是右括号,则栈顶元素依次出栈直至遇到左括号。

队列

  • 只允许在一端进行插入操作,而在另一端进行删除操作端线性表

在Java中队列Queue是一个接口

boolean add(E e);//添加元素
boolean offer(E e);//使用容量限制队列时优于add
E remove();//移除头部元素并返回,如果为空则抛出异常
E poll();//移除头部元素并返回,如果为空则返回null
E element();//只返回头部元素,为空异常
E peek();//返回头部元素,为空null

看C++中的Queue

empty()
Test whether container is empty (public member function )
size()
Return size (public member function )
front()
Access next element (public member function )
back()
Access last element (public member function )
push()
Insert element (public member function )
emplace() 
Construct and insert element (public member function )
pop()
Remove next element (public member function )
swap() 
Swap contents (public member function )两个队列进行交换

循环队列

队列的头尾相接的顺序存储结构称为循环队列。 一个数组,两个指针,一个指向头front,一个指向尾巴后面rear,尾指针到末端后,返回至数组最前端。队列满时为:(rear+1)%QueueMaxSize==front. 计算队列大小:(rear-front+QueueMaxSize)%QueueMaxSize ** **

  • 串是由零个或多个字符组成的有限序列,有名叫字符串。

字符串能比较大小”aaa”<”aaaaa” private final byte[] value;

朴素算法模式匹配

两个循环嵌套 低效

KMP模式匹配算法

Knuth-Morris-Pratt 字符串查找算法,简称为 “KMP算法”,常用于在一个文本串S内查找一个模式串P 的出现位置,这个算法由Donald Knuth、Vaughan Pratt、James H. Morris三人于1977年联合发表,故取这3人的姓氏命名此算法。

下面先直接给出KMP的算法流程(如果感到一点点不适,没关系,坚持下,稍后会有具体步骤及解释,越往后看越会柳暗花明☺):

  • 假设现在文本串S匹配到 i 位置,模式串P匹配到 j 位置
  • 如果j = -1,或者当前字符匹配成功(即S[i] == P[j]),都令i++,j++,继续匹配下一个字符;
  • 如果j != -1,且当前字符匹配失败(即S[i]!=P[j]),则令i不变,j=next[j]。此举意味着失配时,模式串P相对于文本串S向右移动了j - next [j] 位。
  • 换言之,当匹配失败时,模式串向右移动的位数为:失配字符所在位置 - 失配字符对应的next 值(next 数组的求解会在下文的3.3.3节中详细阐述),即移动的实际位数为:j - next[j],且此值大于等于1。

很快,你也会意识到next 数组各值的含义:代表当前字符之前的字符串中,有多大长度的相同前缀后缀。例如如果next [j] = k,代表j 之前的字符串中有最大长度为k 的相同前缀后缀。

此也意味着在某个字符失配时,该字符对应的next 值会告诉你下一步匹配中,模式串应该跳到哪个位置(跳到next [j] 的位置)。如果next [j] 等于0或-1,则跳到模式串的开头字符,若next [j] = k 且 k > 0,代表下次匹配跳到j 之前的某个字符,而不是跳到开头,且具体跳过了k 个字符。

转换成代码表示,则是:

int KmpSearch(char* s, char* p)
{
	int i = 0;
	int j = 0;
	int sLen = strlen(s);
	int pLen = strlen(p);
	while (i < sLen && j < pLen)
	{
		//①如果j = -1,或者当前字符匹配成功(即S[i] == P[j]),都令i++,j++    
		if (j == -1 || s[i] == p[j])
		{
			i++;
			j++;
		}
		else
		{
			//②如果j != -1,且当前字符匹配失败(即S[i] != P[j]),则令 i 不变,j = next[j]    
			//next[j]即为j所对应的next值      
			j = next[j];
		}
	}
	if (j == pLen)
		return i - j;
	else
		return -1;
}