《算法导论》笔记 第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。