C语言:3.31
一、链表真题:2019年42题
解析:任何408真题但凡涉及链表的一定都会需要定义一个头节点,很容易被“初始时队列为空”这个条件迷惑,实际上只有一个头节点也是为空。
上面这个有关“头节点”的理解也没错,但这道题的关键在于:被视为“头节点”的那个节点是在变化的,而非静止不动的那一个头节点,可以类比“顺序表的循环队列”也需要一个空位来标志着队满。
事实上:将顺序表的直接类比过来就行了。
整体代码:
//首先定义链表节点
typedef struct LinkNode
{
int val;
LinkNode* next;
}LinkNode;
//链表结构体
typedef struct
{
LinkNode* front;
LinkNode* rear;
}QueueList;
QueueList Q;
//初始化链表
void InitQ(QueueList &Q)
{
LinkNode* q = new LinkNode();
q->next = q;
Q.front = Q.rear = q;
}
//入队
bool Push(QueueList &Q,int x)
{
if (Q.rear->next == Q.front)
{
LinkNode* q = new LinkNode();
Q.rear->val = x;
Q.rear->next = q;
Q.rear = q;
q->next = Q.front;
}
Q.rear->val = x;
Q.rear = Q.rear->next;
return true;
}
//出队
bool Pop(QueueList &Q, int &x)
{
if (Q.front == Q.rear) return false;
x = Q.front->val;
Q.front = Q.front->next;
}
二、13.4:树与二叉树原理解析
1、二叉树层次建树,必须用到“辅助队列”
忍不住了,所以放弃这个层次建树,转向“前序遍历建树”:一步一步写数据结构(二叉树的建立和遍历,c++) - Jymoon - 博客园
发现更垃圾了。
2、二叉树的前序、中序、后序遍历
简单
3、二叉树的层序遍历
使用辅助队列,先将根节点入队,开启循环,循环条件:队列是否为空,队列出队,判断左节点是否为空,若不为空则入队;判断右节点是否为空,不为空则入队。
4、真题:2014年41题
还是比较基础的题目
#include<iostream>
#include"function.h"
using namespace std;
void CreateTree(BTree& T)
{
int x;
cin >> x;
if (x != 0)
{
BTree p = new BNode(x);
T = p;
CreateTree(p->left);
CreateTree(p->right);
}
}
int WPL(BTree T,int x)
{
if (T->left == NULL && T->right == NULL)
{
return T->data * x;
}
else
{
int l = WPL(T->left, x + 1);
int r = WPL(T->right, x + 1);
return l + r;
}
}
//二叉树前序遍历建树
int main()
{
//树根
BTree tree=NULL;
CreateTree(tree);
cout << WPL(tree,0) << endl;
}
三、01基础课:栈与队列
1、卡特兰数
2、共享栈
节省存储空间又降低了上溢的风险,解释如下:
不需要判断是否为头节点,因为不带头节点的链表栈顶会初始化为NULL;
3、循环队列
(1)依旧是线性的逻辑结构,通过“取模”操作使其成为一个环;
(2)若不采用别的手段,则队满和队空的判断条件都会是:rear=front
手段一:加一个空位(很熟悉了)
手段二:增设结构成员size,表示队列中到底多少数量的元素。
手段三:增设标志位tag
每次入队操作都将tag位置为1,表示:上一次做的操作是入队;
每次出队操作将tag为置为0,表示:上一次做的操作是出队;
若rear=front,且tag=1,表示:上一步入队导致的两个指针相等,则队列满;否则队列空。
4、带头节点的链式队列
front永远指向头节点;
注意:链队出队时要考虑是否是最后一个节点,如果是的话,同时要修改尾指针。
带头节点判断队空:rear=front
不带头节点判断队空:rear=NULL,front=NULL
而不带头节点的链栈这俩相等是插入一个节点后,并非队空。
四、栈和队列的应用
1、括号匹配(leetcode原题)
class Solution {
public:
bool isValid(string s) {
stack<char>my;
int len=s.size();
for(int i=0;i<len;i++)
{
if(s[i]=='('||s[i]=='{'||s[i]=='[') my.push(s[i]);
else if(s[i]==')')
{
if(my.empty()) return false;
char x=my.top();
if(x!='(') return false;
my.pop();
}
else if(s[i]==']')
{
if(my.empty()) return false;
char y=my.top();
if(y!='[') return false;
my.pop();
}
else
{
if(s[i]=='}'||my.empty())
{
if(my.empty()) return false;
char z=my.top();
if(z!='{') return false;
my.pop();
}
}
}
if(my.empty())
{
return true;
}
else{
return false;
}
}
};
使用栈,若是"([{",则直接入栈;若是")]}",则出栈判断栈顶元素是否为与其匹配的括号。
若是,则继续走下去;若不是,则直接返回false;若遇到右括号且当前栈为空,则直接返回false
2、中缀表达式转后缀表达式
后缀表达式又称为“逆波兰表达式”,后缀表达式是计算机认识的式子。
需要两个栈,一个结果栈,一个temp栈:
(1)若遇到运算符,则直接入结果栈;
(2)遇到操作符,操作符若为(,则直接入temp栈;
若为),将temp栈内的操作符依次出栈入结果栈,直到遇到(符号;
若为普通操作符,则判断temp栈顶元素与当前操作符的优先级,若大于等于栈顶操作符,则依次出栈直至小于;
若小于则当前操作符入temp栈。
最后结果栈内从栈底到栈顶的顺序就是结果。
注:左小括号“(”的优先级对于temp栈内元素最高,但对于temp栈外元素最低。
3、后缀表达式求值
需要一个存储操作数的栈op,遇见操作数时就将其入栈,遇见操作符时,就从栈顶出栈两个操作数,先出栈的是右操作数,随后将计算结果再次存入栈中。
最后栈低的元素就是最终结果。
4、中缀表达式求值
把2与3两个过程结合起来,依旧是两个栈,一个temp栈存放操作符,一个结果栈存放操作数,当temp内弹出操作符时,按照3的过程从结果栈中弹出操作数,运算后再次将本次结果放入结果栈中。
五、数组
1、二维数组
记住从(0,0)开始数的行优先:
其实仔细看一下以(0,0)开始的下标(i,j)的含义:就是前面有i行j列
若以(1,1)开始,则减1就好了。
六、特殊矩阵
1、对称矩阵
注意:数组一般下标从0开始,矩阵的下标一般从1开始。
求的是元素在数组中的下标。
注意:若存储的是下三角区和主对角线,那么求上三角区 Aij在数组中的位置要转化为下三角。
2、三角矩阵:某半区全为常数k
多存一个k好了,前面有几个元素其在数组内的下标就是几。
3、三对角矩阵,带状矩阵
(1)定义:除了主对角线以及上面、下面的对角线以外,其余元素全为0。
(2)确认位置:关键在于怎么确定Aij在本行是第几个元素:
Aii在本行是第2个元素,所以:j-i+2
(3)反着来
告诉你Aij在一维数组中的下标是k,那么i、j、k的关系是什么?
知道一维数组的下标k,可以推出是在矩阵中的本行的第几个元素。
4、稀疏矩阵
三元组:行号+列号+值
能通过只看右边的三元组从而还原整个稀疏矩阵么,答案是否定的。
因为并不知道原来的稀疏矩阵共有几行几列。
所以:还需要两个变量分别存行数与列数。
七、字符串匹配
1、暴力匹配
时间复杂度:O(n²)
2、KMP算法
(1)三个基本定义
前缀:不包括串中最后一个字符的所有前缀
后缀:不包括串中第一个字符的所有后缀
PM:最长的相同缀的长度,而非数量。
(2)机械的求next数组
求出每个子串的部分匹配值;将其右移;首位补-1
(3)求nextval数组
i是否等于next[i],若相等则修改next[i]=next[next[i]];
因为之所以要用next数组,一定是发生了失配,则i不等于j,若next[i]=i,那么j也一定失配;
所以要修改。
注意:势必从前往后修改
八、顺序查找及原理
1、2009年真题
不管是寻找链表中的哪一个具体位置的节点,都可以使用“双指针法”
代码如下:
#include<iostream>
using namespace std;
//首先定义链表节点
typedef struct LinkNode
{
int data;
LinkNode* next;
}LinkNode,*LinkList;
//初始化
void InitLinkList(LinkList &L)
{
L = new LinkNode();
L->next = NULL;
}
//尾插法建立链表
void Insert(LinkList& L)
{
//定义一个尾指针
LinkNode* rear = L;
int x;
while (cin >> x && x != 0)
{
LinkNode* p = new LinkNode();
p->data = x;
p->next = NULL;
rear->next = p;
rear = p;
}
}
int K(LinkList L, int k)
{
LinkNode* pre = L;
LinkNode* post = L;
while (k--)
{
pre = pre->next;
if (!pre) return 0;
}
while (pre)
{
pre = pre->next;
post = post->next;
}
cout << post->data << endl;
return 1;
}
int main()
{
//声明一个链表头节点
LinkList L;
//初始化链表头节点
InitLinkList(L);
Insert(L);
int y = K(L, 5);
}
2、顺序查找代码
#include<iostream>
using namespace std;
//之前的顺序表用静态数组实现,现在用堆实现
typedef struct
{
int* T;
int len;
}STable;
void Init(STable& S, int len)
{
//多申请了一个位置作为哨兵
S.len = len + 1;
S.T = new int[S.len];
for (int i = 0; i < S.len; i++)
{
S.T[i] = i;
}
}
//进行查找过程
int Search(STable& S, int x)
{
for (int j = S.len - 1; j > 0; j--)
{
if (x == S.T[j]) return j;
}
return 0;
}
int main()
{
STable s;
Init(s, 10);
int y = Search(s, 5);
cout << y << endl;
}
九、折半查找实战
折半查找又称“二分查找”,仅适用于有序的顺序表;
(1)对数组进行排序
与c++内容器排序一样,都需要给出数组的起始地址与终止地址,所以是:
//对数组进行排序
sort(s.T, s.T+s.len);
(2)整体代码
#include<iostream>
#include<algorithm>
using namespace std;
//之前的顺序表用静态数组实现,现在用堆实现
typedef struct
{
int* T;
int len;
}STable;
void Init(STable& S, int len)
{
//多申请了一个位置作为哨兵
S.len = len + 1;
S.T = new int[S.len];
for (int i = 0; i < S.len; i++)
{
S.T[i] = i;
}
}
int Search(STable S, int x)
{
int low = 0, high = S.len - 1;
while (low <= high)
{
int mid = (low + high) / 2;
if (S.T[mid] == x) return mid;
else if (S.T[mid] > x)
{
high = mid - 1;
}
else low = mid + 1;
}
return -1;
}
int main()
{
STable s;
Init(s, 10);
//对数组进行排序
sort(s.T, s.T+s.len);
//折半查找
int y = Search(s, 555);
cout << y << endl;
}