张桄玮
郑州一中(Legacy)
回顾: 程序=算法+数据结构
今天上午: 基本的数据结构
遵循的逻辑
神经的定义说: 链表是
实际上是链表的递归定义(DP的时候很有用)
想法1: 把它们放到 “隔间” 里面, 对隔间(内存池)编号.
很好的想法!
问题: 如果下一个没有了, 怎么办?
疑问: 这不还是数组吗?
int head, // 这个链表的头是哪个节点
idx, // 当前链表里面有几个节点
val[MAXN], // val[i]:=编号为i的格子里的数据
nxt[MAXN]; // nxt[i]:=编号为i的格子的下一个格子编号
快速问答: 构成的链表是什么?
初始状态: 链表中没有任何元素
维护一个数据结构 = 让所有的数据和指针恢复正确的位置
哪些需要改变?
哪些需要改变?
nxt
nxt
next
指向next
的next
nxt[k] = nxt[nxt[k]];
idx
要idx-=1
吗?为什么我们总是说插入/删除格子的后面的元素?
需要知道我前面是谁
解决方法
结构
int val[N], // val[i]:=表示结点i存储的数值
prv[N], // prv[i]:=节点i的前一个节点是哪个节点
nxt[N], // nxt[i]:=节点i的下一个节点是哪个节点
idx; // 内存池现在用了几个节点
问题: 怎么初始化?
是不是可以加一个 “空白” 节点, 表示这是结束?
画一个草图
还是, 画一个草图
//删除内存池里面编号为k的点
void remove(int k) {
//就是将k的左端点和右端点相互连接
prv[nxt[k]] = prv[k];
nxt[prv[k]] = nxt[k];
}
其中
prv[i]
变为了dlls[i].prv
,
看上去变得清楚一些, 但是打的字长一点习题: P1160 队列安排
回顾刚刚的内存池带来的 “隐患”(不太可能在OI发生, 但是提一下)
active
,
一个是free
free
里面薅一个加到active
里面active
里面把它断掉加到free
里面马上: 介绍指针
关注变量的: 类型/里面存的值/地址
变量存在哪? 内存的 “单元” 里面
Type *
(pointer to
type)指针: A pointer is a variable that contains the address of a variable
链表结构体
void __attribute__((unused)) inspect_tnode(TSKLST *bd){
task_t *start = &bd->dummy;
// Log("%p\n", start);
int count = 0;
start = start->nxt;
while(start != &bd->dummy){
panic_on(start->prv->nxt != start, "Did not maintain the llist well.");
printf("[%s]->", start->name);
count++;
start = start->nxt;
}
printf("NIL\n");
panic_on(count != bd->nr_node, "Did not maintain size well.");
}
panic_on
是啥?
xxx
的条件下恐慌(退出程序)怎么实现?
空指针解引用的时候会引发SIGSEGV
(段错误)
list
参看stl-list.cpp
如果有2个或者多个链表, 将他们组成结构体是个好主意
栈: stack: 一叠/堆叠
生活中的实例
模拟方式: 使用数组, 追踪栈顶是谁
练习: B3614 栈
题目大意: 判定一个序列是否可以由栈构成另一个序列?
栈的操作:
另一种观点
词法分析: 简单(数字只有一位!)
总体思路:
第一个问题: 优先级
第二个问题: 结合律
+-*/
是左结合的: \(2+3+4=((2+3)+4)\)^
与^
相同的时候要入栈细节: 我怎么输出?
好麻烦!
有一类稍微简单一点的, 考虑表达式的递归特性
每次选出优先级最低的那个节点, 并且在两边分裂!
练习题
练习: 将下面的函数改写为非递归的形式
void hanoi(int n, char from, char to, char via) {
if (n == 1) printf("%c -> %c\n", from, to);
else {
hanoi(n - 1, from, via, to);
hanoi(1, from, to, via);
hanoi(n - 1, via, to, from);
}
return;
}
stack
只能访问最顶上的元素
push
和pop
使用链表模拟队列:
头 \(\to\) 尾
练习: B3616
如果要用数组模拟
处理办法:
bool push(int e){
if((rear+1)%NR_DAT==front) return false; // full!
data[rear] = e;
rear = (rear+1)%NR_DAT;
return true;
}
bool pop(int &e){
// ^ Using reference will make the argument change
// equivalent to int *eptr
if(front == rear) return false;
e = data[front];
// *etpr = data[front];
front = (front + 1)%NR_DAT;
return true;
}
queue
尾部入队, 头部出队
链表: 这我熟!
数组: (长度为 \(n\) )不考虑溢出的话就是一个带着head和tail的栈
INC(x) -> (x+1)%NR_DAT
DEC(X) -> (x-1+NR_DAT)%NR_DAT
STL: deque
(读音deck, /dēˈkyo͞o/是dequeue, 出队的意思)
deque
内部实现还是使用的数组
求一个窗口里面的最大值/最小值
最小值 | 最大值 | ||||||||
---|---|---|---|---|---|---|---|---|---|
\([1\) | 3 | \(-1]\) | -3 | 5 | 3 | 6 | 7 | -1 | 3 |
1 | \([3\) | -1 | \(-3]\) | 5 | 3 | 6 | 7 | -3 | 3 |
1 | 3 | \([-1\) | -3 | \(5]\) | 3 | 6 | 7 | -3 | 5 |
1 | 3 | -1 | \([-3\) | 5 | \(3]\) | 6 | 7 | -3 | 5 |
1 | 3 | -1 | -3 | \([5\) | 3 | \(6]\) | 7 | 3 | 6 |
1 | 3 | -1 | -3 | 5 | \([3\) | 6 | \(7]\) | 3 | 7 |
核心操作
最小的队列如上所示;
最大的队列就是把某个符号反过来.
练习: P1440 求 \(m\) 区间的最小值