《算法导论》笔记 第10章 10.2 链表
【笔记】
哨兵不能降低各种操作的渐进时间界,但可以降低常数因子,简化代码。
template <typename ITEM>
class list {
private:
struct DATA{
ITEM key;
DATA *prev, *next;
DATA() {}
DATA(ITEM x):key(x) {}
}NIL;
DATA *nil;
int sz;
public:
list(){
nil = &NIL;
nil->next=nil;
nil->prev=nil;
}
~list(){
while (sz) remove(nil->next);
}
void insert(ITEM x) {
DATA *p = new DATA(x);
p->next = nil->next;
nil->next->prev = p;
nil->next = p;
p->prev = nil;
sz++;
}
bool search(ITEM k) {
DATA* p = nil->next;
while (p != nil && p->key != k) p = p->next;
return p != nil;
}
void remove(DATA* p) {
p->prev->next = p->next;
p->next->prev = p->prev;
delete p;
sz--;
}
};【练习】
10.2-1 动态集合上的操作INSERT能否用一个单链表在O(1)时间内实现?对DELETE操作呢?
INSERT 能在O(1)内实现,DELETE不能。
10.2-2 用一单链表L实现一个栈,要求PUSH和POP操作的时间仍为O(1)。
const int MAXN = 10;
struct NODE{
int key;
NODE *next;
NODE():key(0) {}
NODE(int x):key(x) {}
};
NODE* head[MAXN];
void prepare(int L) {
head[L] = NULL;
}
NODE* search(int L,int k) {
NODE* p = head[L];
while (p!=NULL && p->key!=k) p = p->next;
return p;
}
void insert(int L,int x) {
NODE *p = new NODE(x);
p->next = head[L];
head[L] = p;
}
void remove(int L,NODE *p) {
if (p==NULL) return;
if (head[L] == p) {
head[L] = p->next;
delete p;
return;
}
NODE *t = head[L];
while (t->next!=NULL && t->next!=p) t = t->next;
if (t->next==NULL) return;
t->next = p->next;
delete p;
}
void push(int L,int x) {
insert(L,x);
}
int pop(int L) {
if (head[L]==NULL) return -1;
int res = head[L]->key;
remove(L,head[L]);
return res;
}10.2-3 用一单链表L实现一个队列,要求ENQUEUE和DEQUEUE操作的时间仍为O(1)。
struct NODE{
int key;
NODE *next;
NODE():key(0) {}
NODE(int x):key(x) {}
};
NODE *head[MAXN],*tail[MAXN];
void prepare(int L) {
head[L] = NULL;
tail[L] = NULL;
}
void push(int L,int x) {
NODE *p = new NODE(x);
p->next = NULL;
if (head[L] == NULL) head[L] = p;
if (tail[L] != NULL) tail[L]->next = p;
tail[L] = p;
}
int pop(int L) {
if (head[L]==NULL) return -1;
int res = head[L]->key;
NODE *p = head[L];
head[L] = head[L]->next;
delete p;
return res;
}10.2-4 如前文所写,LIST-SEARCH'过程的每一次循环迭代都需要做两个测试:一个检查x!=nil[L],一个检查key[x]!=k。说明如何能够在每次迭代中,省去对x!=nil[L]的检查。
将哨兵的值赋为要查找的值,在循环结束后判断找到的结点是否为哨兵即可。
10.2-5 用环形单链表来实现字典操作INSERT、DELETE和SEARCH,并给出它们的运行时间。
O(1) O(n) O(n) ?
10.2-6 动态集合操作UNION以两个不想交的集合S1和S2作为输入,输出集合S=S1∪S2包含了S1和S2的所有元素。该操作常常会破坏S1和S2。说明应如何选用一种数据结构,以便支持在O(1)时间内的UNION操作。
采用链表,head[L]指向表头,tail[L]指向表尾。合并时将tail[S1]->next 指向head[S2]。新集合的表头为head[S1]。
10.2-7 请给出一个θ(n)时间的非递归过程,它对含n个元素的单链表的链进行逆转。除了链表本身占用的空间外,该过程应仅使用固定量的存储空间。
void reverse(int L) {
NODE *pre, *p, *nxt;
pre = NULL;
p = head[L];
nxt = p->next;
while (p) {
if (nxt == NULL) {
head[L] = p;
break;
}
p->next = pre;
pre = p;
p = nxt;
nxt = nxt->next;
}
}*10.2-8 说明如何对每个元素仅用一个指针np[x]来实现双链表。
用异或即可,由a^b^b=a可知,np[x]中储存的是next[x]^prev[x]。
从前向后遍历时,prev[x]已知,令pt为prev[x],next[x]即为np[x]^pt,pt初值存在head[L]中。
从后向前遍历时,next[x]已知,令pt为next[x],prev[x]即为np[x]^pt。
浙公网安备 33010602011771号