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


你可能感兴趣的:(《算法导论》笔记 第10章 10.2 链表)