设计链表

pre1. 设计心得

  1. 无论是链表的增删改查某个节点,都可以通过找到该节点的前一个前一个节点实现。所以说,查找某个节点(或者 \(index\))的前一个节点是一个很常用的操作,因此可以把它单独拿出来,然后被其他函数重复利用。
  2. 链表的从头添加和从尾添加节点都可以利用“从某个节点之前添加节点”实现,因此不需要单独实现头增和尾增,直接利用该函数即可。


pre2. bug

当我们用一个 \(struct\) 封装了一个链表的时候,例如:

struct ListNode {
    int val;
    struct ListNode *next;
};

struct myList {
    int size;
    struct ListNode *head;
};

在上面的 \(demo\) 中,我们用一个结构体 \(myList\) 封装了一个结构体 \(ListNode\)
当我们声明一个 \(myList\) 类型的指针并为其分配内存时,如果这样:

struct myList *L = (myList *)malloc(sizeof(struct myList));

大概率是错误的。(为什么是大概率等会再解释)
因为我们并没有为其内部的struct ListNode *head分配内存,我们仅仅只是为一个指针分配了 \(memory\),但这个指针没有指向任何地方
即,这个指针是一个野指针,出现野指针是很危险的事情!
不过,如果我们不使用 \(head\),不就不会出问题了吗?确实是这样的,但是我们需要显式的把 \(head\) 声明为 \(null\)
同样,在我们 \(free\) 掉一个 \(myList\) 类型的变量时,要先 \(free\)\(head\) 指向的 \(memory\),然后才能 \(free(myList)\)



1. 题目描述

设计一个链表,实现基本操作(增删改查)



2. 单链表

/*============================================
            some instructinos:

pre   : leetcode's List always define as follows
type  : this link is single Link with head node

struct ListNode{
    int val;
    struct ListNode *next;
};  
============================================*/

#define MAX(a,b) (((a)>(b)) ? (a) : (b))

typedef struct {
    struct ListNode *head;
    int size; // Link_length
} MyLinkedList;

/*============================================*/

struct ListNode* ListNodeCreate(int val)
{ 
    // you should ues : sizeof(struct ListNode)
    // but            : sizeof struct ListNode
    struct ListNode *newNode = (struct ListNode *)malloc(sizeof(struct ListNode));
    newNode->val = val;
    newNode->next = NULL;
    return newNode;
}

MyLinkedList* myLinkedListCreate() {
    MyLinkedList *L = (MyLinkedList *)malloc(sizeof(MyLinkedList));
    // there we only alloc memory for myLinkedList
    // but we not alloc memory for myLinkedList->head !!!
    // it's very easy to forget to malloc it and get bug that very difficult to debug
    L->head = ListNodeCreate(0);
    L->size = 0;
    return L;
}


/*============================================*/

int myLinkedListGet(MyLinkedList* L, int index) {
    int length = L->size;
    if(length == 0 || index < 0 || index >= length) return -1;
    
    struct ListNode *cur = L->head->next;
    for(int i = 0; i < index; i ++ )    cur = cur->next;
    return cur->val;
}

/*============================================*/


void myLinkedListDeleteAtIndex(MyLinkedList* L, int index) {
    int length = L->size;
    if(length == 0 || index < 0 || index >= length) return ;
    
    struct ListNode *prev = L->head;
    for(int i = 0; i < index; i ++ )    prev = prev->next;
    struct ListNode *delNode = prev->next;
    prev->next = prev->next->next;
    free(delNode);
    L->size -- ;
}

void myLinkedListFree(MyLinkedList* L) {
    struct ListNode *cur = L->head;   
    // head node also have memory
    // so we have to should to free it
    while(cur)
    {
        struct ListNode *tmp = cur;
        cur = cur->next;
        free(tmp);
    }
    free(L);
}

/*============================================*/

// index start from 0
void myLinkedListAddAtIndex(MyLinkedList* L, int index, int val) {
    int length = L->size;
    
    // special check at first
    if(index > length) return ;
    
    // if index < 0 then index is assignmented as 0, can reduce if check
    index = MAX(0, index); 
    
    // get prev node
    struct ListNode *prev = L->head;
    for(int i = 0; i < index; i ++ )    prev = prev->next;
    
    // add new node
    struct ListNode *newNode = ListNodeCreate(val);
    newNode->next = prev->next;
    prev->next = newNode;
    L->size ++ ;
}

void myLinkedListAddAtHead(MyLinkedList* L, int val) {
    myLinkedListAddAtIndex(L, 0, val); // completee by myLinkedListAddAtIndex
}

void myLinkedListAddAtTail(MyLinkedList* L, int val) {
    myLinkedListAddAtIndex(L, L->size, val);        // complete by myLinkedListAddAtIndex
}


/*============================================*/


/**
 * Your MyLinkedList struct will be instantiated and called as such:
 * MyLinkedList* obj = myLinkedListCreate();
 * int param_1 = myLinkedListGet(obj, index);
 
 * myLinkedListAddAtHead(obj, val);
 
 * myLinkedListAddAtTail(obj, val);
 
 * myLinkedListAddAtIndex(obj, index, val);
 
 * myLinkedListDeleteAtIndex(obj, index);
 
 * myLinkedListFree(obj);
*/


3. 双链表

/*==========================================*/
/*       double linked list version         */
/*  LeetCode no double linked list version  */
/*    so we have to implement it ourselves  */
/*==========================================*/

#define MAX(a,b) (((a)>(b)) ? (a) : (b))

typedef struct DLinkList {
    int val;
    struct DLinkList *prev, *next;
} DLinkListNode;

typedef struct {
    // just as single link has one guard: head
    // double single link need two guars: head nad tail
    struct DLinkList *head, *tail;  
    int size;
} MyLinkedList;

/*==========================================*/

DLinkListNode* DLinkListNodeCreate(int val) {
    DLinkListNode *newNode = (DLinkListNode *)malloc(sizeof(DLinkListNode));
    newNode->val = val;
    newNode->prev = NULL;
    newNode->next = NULL;
    return newNode;
}

MyLinkedList* myLinkedListCreate() {
    MyLinkedList *L = (MyLinkedList *)malloc(sizeof(MyLinkedList));
    L->size = 0;
    L->tail = DLinkListNodeCreate(0);
    L->head = DLinkListNodeCreate(0);
    L->head->next = L->tail;
    L->tail->prev = L->head;
    return L;
}

/*==========================================*/

void myLinkedListAddAtIndex(MyLinkedList* L, int index, int val) {
    int length = L->size;
    if(index > length) return ;
    
    /*
        there we can there some optimizations to use dlink's tail and head
        if the index is close to the head, start traveling from the head
        else, it will start from the tail
    */
    index = MAX(0, index);
    DLinkListNode *preNode; // prev is the node pre the node[idx]
    if(index < length - index) { // traveling from head
        preNode = L->head;
        for(int i = 0; i < index; i ++ )
            preNode = preNode->next;
    }
    else { // traveling from end
        preNode = L->tail;
        index = 1 + (length - index);
        for(int i = 0; i < index; i ++ )
            preNode = preNode->prev;
    }
    
    DLinkListNode *newNode = DLinkListNodeCreate(val);
    newNode->prev = preNode;
    newNode->next = preNode->next;
    preNode->next->prev = newNode;
    preNode->next = newNode;
    L->size ++ ;
}

void myLinkedListAddAtHead(MyLinkedList* L, int val) {
    myLinkedListAddAtIndex(L, 0, val);
}

void myLinkedListAddAtTail(MyLinkedList* L, int val) {
    myLinkedListAddAtIndex(L, L->size, val);
}

/*==========================================*/

int myLinkedListGet(MyLinkedList* L, int index) {
    int length = L->size;
    if(index >= length || index < 0)  return -1;
    
    DLinkListNode *cur;
    // to index + 1 not to index because head is a guard
    // if index == 0, we need go next step as 1
    // also a optimization
    if(index + 1 < length - index) {
        cur = L->head;
        for(int i = 0; i < index + 1; i ++ )
            cur = cur->next;
    }
    else {
        cur = L->tail;
        index = length - index;
        for(int i = 0; i < index; i ++ )
            cur = cur->prev;
    }
    
    return cur->val;
}

/*==========================================*/

void myLinkedListDeleteAtIndex(MyLinkedList* L, int index) {
    int length = L->size;
    if(index < 0 || index >= length)    return ;
    
    DLinkListNode *preNode;
    if(index < length - index) {
        preNode = L->head;
        for(int i = 0; i < index; i ++ )
            preNode = preNode->next;
    }
    else {
        preNode = L->tail;
        index = 1 + (length - index);
        for(int i = 0; i < index; i ++ )
            preNode = preNode->prev;
    }
    
    DLinkListNode *p = preNode->next;
    p->next->prev = preNode;
    preNode->next = p->next;
    free(p);
    L->size -- ;
}

void myLinkedListFree(MyLinkedList* L) {
    DLinkListNode *cur = NULL, *tmp = NULL;
    for(cur = L->head; cur; ) {
        tmp = cur;
        cur = cur->next;
        free(tmp);
    }
    free(L);
}

/*==========================================*/


/**
 * Your MyLinkedList struct will be instantiated and called as such:
 * MyLinkedList* obj = myLinkedListCreate();
 * int param_1 = myLinkedListGet(obj, index);
 
 * myLinkedListAddAtHead(obj, val);
 
 * myLinkedListAddAtTail(obj, val);
 
 * myLinkedListAddAtIndex(obj, index, val);
 
 * myLinkedListDeleteAtIndex(obj, index);
 
 * myLinkedListFree(obj);
*/


4. 双向链表版本2

class MyLinkedList {
public:
    struct ListNode {
        int val;
        ListNode *next, *prev;
        ListNode(int v = 0, ListNode *n = nullptr, ListNode *p = nullptr) 
            : val(v), next(n), prev(p) {}
    };
private:
    int size;
    struct ListNode *head, *tail;
/*
    head -> noed1 -> node2 -> ... -> tail
*/
public:
    MyLinkedList() {
        head = new ListNode;
        tail = new ListNode;
        head->next = tail;
        tail->prev = head;
        size = 0;
    }
    ~MyLinkedList() {
        for(int i = 0; i < size; i ++ ) deleteAtIndex(0);
        delete head;
        delete tail;
    }
    ListNode *find_pre(int index)
    {
        ListNode *cur = nullptr; // cur 指向 index的前面的节点
        if(index > size / 2) {
            // 从右边搜索更快速
            cur = tail;
            for(int i = 0; i < size - index + 1; i ++ )    cur = cur->prev;
        }
        else {
            cur = head;
            for(int i = 0; i < index; i ++ ) cur = cur->next;
        }
        return cur;
    }

    int get(int index) {
        if(index < 0 || index >= size)  return -1;
        ListNode *cur = nullptr;
        cur = find_pre(index);
        return cur->next->val;
    }

    void addAtIndex(int index, int val) {
        if(index > size)    return ;
        if(index < 0)   index = 0;
        ListNode *newNode = new ListNode(val);
        ListNode *cur = nullptr; // cur 指向 index的前面的节点
        cur = find_pre(index);
        newNode->next = cur->next;
        newNode->prev = cur;
        cur->next->prev = newNode;
        cur->next = newNode;
        size ++ ;
    }

    void addAtHead(int val) {
        addAtIndex(0, val);
    }
    
    void addAtTail(int val) {
        addAtIndex(size, val);
    }
    
    void deleteAtIndex(int index) {
        if(index < 0 || index >= size)  return ;
        ListNode *cur = find_pre(index);
        ListNode *tmp = cur->next;
        cur->next = tmp->next;
        tmp->next->prev = cur;
        cout << "delete: " << tmp->val << endl;
        delete tmp;
        size -- ;
    }
};
/*
["MyLinkedList","addAtHead","deleteAtIndex","addAtHead","addAtHead","addAtHead","addAtHead","addAtHead","addAtTail","get","deleteAtIndex","deleteAtIndex"]
[[],[2],[1],[2],[7],[3],[2],[5],[5],[5],[6],[4]]
*/


/**
 * Your MyLinkedList object will be instantiated and called as such:
 * MyLinkedList* obj = new MyLinkedList();
 * int param_1 = obj->get(index);
 * obj->addAtHead(val);
 * obj->addAtTail(val);
 * obj->addAtIndex(index,val);
 * obj->deleteAtIndex(index);
 */


5. 单向循环链表

创建方法

关于带头节点的单向循环链表,有两种创建方法:

  1. 链表结束后指向头(哨兵)结点
  2. 链表结束后指向第一个节点
    IMG

我这里使用的是第一种创建方法。


遍历方法

如果我们想要遍历列表,由于链表循环,会无限循环,可以通过 cur != head 作为终止条件,cur 表示当前正在遍历的节点,\(head\) 表示头结点,当我们再一次遍历到头结点的时候,一次完整的链表遍历就完成了。

class MyLinkedList {
private:
    int size; // 下标从 0 开始
    ListNode *head;
    
public:
    MyLinkedList() {
        size = 0;
        head = new ListNode();    
        head->next = head;
    }
    
    ~MyLinkedList() {
        for(int i = 0; i < size; i ++ ) {
            deleteAtIndex(0);
        }
        delete head;
    }
    
    ListNode *find_pre(int index) {
        ListNode *cur = head;
        for(int i = 0; i < index; i ++ )    cur = cur->next;
        return cur;
    }
    
    int get(int index) {
        if(index < 0 || index >= size)  return -1;
        ListNode *pre = find_pre(index);
        return pre->next->val;
    }
    
    void addAtIndex(int index, int val) {
        if(index < 0 || index > size) {
            cout << "[Error] wrong index:" << index << endl;
            return ;
        }
        ListNode *pre = find_pre(index);
        ListNode *newNode = new ListNode(val, pre->next);
        pre->next = newNode;
        size ++ ;
    }
    
    void addAtHead(int val) {
        addAtIndex(0, val);
    }
    
    void addAtTail(int val) {
        addAtIndex(size, val);
    }
    
    void deleteAtIndex(int index) {
        if(index < 0 || index >= size)   return ;
        ListNode *pre = find_pre(index);
        ListNode *tmp = pre->next;
        pre->next = tmp->next;
        delete tmp;
        size -- ;
    }
    
    void travel() {
        ListNode *cur = head;
        cout << "Lsit: ";
        while(cur->next != head) {
            cout << cur->next->val << ' ';
            cur = cur->next;
        }
        cout << endl;
    }
    
    bool empty() {
        return !size;
    }
};


6. 双向循环链表

创建方法

同单向循环链表,我这里实现的也是带头结点(即哨兵)的双向循环链表。
但是不同于普通的双向链表需要一个头结点和一个尾节点,这里只需要一个头结点


为什么要有哨兵位

  1. 对于一个已经创建和初始化的链表来讲,可以没有数据,即头节点,尾节点等,但是不能没有哨兵位,因为哨兵位并不是帮我们存储数据的,只是来定位链表的。即使是一个空链表,它也会有哨兵位,所以我们可以用哨兵位是否为空来检测程序运行情况。
  2. 对于链表来讲,一个很重要的地方就是头的位置,而哨兵位则可以起到定位头的作用,它的下一个节点就是头节点。
  3. 在双向循环链表中,如果我们要遍历,那么很难去说明结束条件,而我们可以用哨兵位来作为结束条件,当当前节点为哨兵位时,遍历结束。

哨兵位的好处还有很多,这里只是简单列举一下。


IMG

struct DoubleListNode {
    int val;
    DoubleListNode *next;
    DoubleListNode *prev;
    DoubleListNode(int v = 0, DoubleListNode *n = nullptr, DoubleListNode *p = nullptr) 
        : val(v), next(n), prev(p) {}
};
    
class MyLinkedList {
private:
    DoubleListNode *head;
    int size;
public:
    MyLinkedList() {
        size = 0;
        head = new DoubleListNode();
        head->next = head;
        head->prev = head;
    }
    
    ~MyLinkedList() {
        for(int i = 0; i < size; i ++ ) deleteAtIndex(0);
        delete head;
    }
    
    DoubleListNode *find_pre(int index) {
        DoubleListNode *cur = head;
        for(int i = 0; i < index; i ++ ) {
            cur = cur->next;
        }
        return cur;
    }
    
    int get(int index) {
        travel();
        if(index < 0 || index >= size)  return -1;
        DoubleListNode *pre = find_pre(index);
        return pre->next->val;
    }
    
    void addAtIndex(int index, int val) {
        if(index > size)    return ;
        index = max(0, index);
        DoubleListNode *pre = find_pre(index);
        DoubleListNode *newNode = new DoubleListNode(val);
        newNode->next = pre->next;
        newNode->prev = pre;
        pre->next->prev = newNode;
        pre->next = newNode;
        size ++ ;
    }   
    
    void addAtHead(int val) {
        addAtIndex(0, val);
    }
    
    void addAtTail(int val) {
        addAtIndex(size, val);  
    }
    
    void deleteAtIndex(int index) {
        if(index < 0 || index >= size)  return ;
        DoubleListNode *pre = find_pre(index);
        DoubleListNode *tmp = pre->next;
        tmp->next->prev = pre;
        pre->next = tmp->next;
        delete tmp;
        size -- ;
    }
    
    void travel() {
        DoubleListNode *cur = head->next;
        cout << "List: ";
        while(cur != head) {
            cout << cur->val << ' ';
            cur = cur->next;
        }
        cout << endl;
    }
    
    bool empty() {
        return !size;
    }
};


posted @ 2022-09-23 09:36  光風霽月  阅读(10)  评论(0编辑  收藏  举报