1.基本概念
链表 (Linked List)是一种线性表,但是在内存中不是按照线性的顺序储存数据,是通过每个节点的指针指向下一个节点的指针来链接。相对于顺序储存(例如数组),链表的插入操作更快( O(1) ),但是失去了随机读取的优点。
链表一般有单向链表,双向链表,循环链表这三种形式。
2.单向链表
该种形式是链表中最简单的,每个节点包含了数据域和指针域,数据域用来保存该节点的值,指针域用来指向下一个节点(链表的尾巴节点应指向NULL),它保存的是下一个节点的地址。
一般完整的链表会有头节点,头节点不储存数据。查找数据的话要从头节点开始,每次访问下一个节点直到查找到数据或到达尾部。
单向链表典型的插入方法有头插法和尾插法,头插法只需要一个头指针,而尾插法还需要一个指向尾部的尾指针。
单向链表的C++实现:
template<class T> struct Node // 节点的数据结构 { T data; Node *next; Node() :next(nullptr){} // 无参构造 Node(T t) :data(t), next(nullptr){} // 带参数构造 }; template<class T> class LinkList //链表类 { private: Node<T> *head; // 头指针 (空节点) Node<T> *tail; // 尾指针 (空节点) int size; public: LinkList() { head = new Node<T>; tail = new Node<T>; size = 0; } ~LinkList() { } public: /** * 在链表的头部插入新节点 * @param val:赋给新节点的值 */ void insertOnHead(T val); /** * 在链表的尾部插入新节点 * @param val:赋给新节点的值 */ void insertOnTail(T val); /** * 在链表的某一位置插入新节点,插入成功返回true,否则返回false * @param i:指定的位置 * @param val:赋给新节点的值 */ bool insert(int i,T val); /** * 获取某节点的值,获取成功返回true,否则返回false * @param i:指定的位置 * @param val:赋给新节点的值 */ bool getData(int i,T &val); /** * 判断空,空的话返回true,否则返回false */ bool isEmpty(); /** * 清空链表 */ void clear(); /** * 打印链表 */ void printList(); }; template<class T> void LinkList<T>::insertOnHead(T val) { Node<T> *newNode = new Node<T>; newNode->data = val; newNode->next = head->next; head->next = newNode; if (!size) tail->next = newNode; // 把尾指针指向第一个节点 ++size; } template<class T> void LinkList<T>::insertOnTail(T val) { Node<T> *newNode = new Node<T>; newNode->data = val; newNode->next = nullptr; tail->next->next = newNode; tail->next = newNode; ++size; } template<class T> bool LinkList<T>::insert(int i,T val) { if (i <= 0|| i > size) return false; Node<T> *newNode = new Node<T>; Node<T> *temp = head; if (i == size) tail->next = newNode; // 如果在最后插入,更新尾指针 for (int j = 0; j <= size; ++j) { if (i != 0) { temp = temp->next; --i; } else { newNode->data = val; newNode->next = temp->next; temp->next = newNode; break; } } ++size; return true; } template<class T> bool LinkList<T>::getData(int i, T &val) { if (i <= size && i > 0) { Node<T> *temp = head; for (int j = 0; j < i; ++j) { temp = temp->next; } val = temp->data; return true; } else { return false; } } template<class T> bool LinkList<T>::isEmpty() { return size ? false : true; } template<class T> void LinkList<T>::clear() { Node<T> *tempFront = head->next; // 指向下一个待删除 Node<T> *tempBack = head->next; // 指向待删除的元素 for (int i = 0; i < size; ++i) { tempBack = tempFront; tempFront = tempFront->next; delete tempBack; } size = 0; } template<class T> void LinkList<T>::printList() { if (!size) { cout << "empty link" << endl; return; } Node<T> * temp = head->next; for (int i = 0; i < size; ++i) { cout << temp->data << endl; temp = temp->next; } }
2.双向链表
和单向链表类似,不过双向链表的每个节点包含一个数据域和两个指针域,一个前向指针和一个后向指针,相对单链表的优点是可以访问前驱而不用从头节点开始,其结构如下图:
其实链表的操作是相似的,都是对节点的连接和断开与销毁,不过双向链表需要注意的是增加了对前向指针的操作,新增节点的图示如下:
双向链表插入的C++示例如下,完整代码见GitHub。
bool insert(int i,T val) { if (i <= 0|| i > size) return false; Node *newNode = new Node; Node *temp = head; if (i == size) tail->next = newNode; // 如果在最后插入,更新尾指针 for (int j = 0; j <= size; ++j) { if (i != 0) { temp = temp->next; --i; } else { newNode->data = val; newNode->pre = temp; newNode->next = temp->next; temp->next = newNode; break; } } ++size; return true; }