数据结构基础(8) --单链表的设计与实现(1)之基本操作
链表简介
数组的缺点:
1.元素插入:除了在数组的末尾插入元素之外,在数组的其他任何位置插入元素都需要进行数组元素的频繁移动(插入位置之后的元素都需往后移动), 时间复杂度约为O(N);
2.数组的删除:除了在数组的末尾删除元素之外,在数组的其他任何位置删除元素都需要进行数组元素的频繁移动(删除位置之后的元素都需往前移动), 时间复杂度也为O(N);
链表的特点:
由于在链表中插入/删除元素都不需要进行数据的移位, 只需要O(1)时间完成, 因此链表适用于频繁插入与删除的情况;
但是链表也有缺点:链表不适用于需要频繁访问的情况, 因为如果需要查询一个数据, 链表需要遍历整个数据序列, 需要的O(n)的时间, 然而由于数组支持随机访问, 因此数组只需O(1)的时间便可完成数据的访问, 因此此时数组就非常便利了!
单链表
单链表特征如图所示:
单链表只需一个节点(首节点first)来指向该链表,有时为了操作方便,在第一个结点之前虚加一个”头结点”(算法导论称之为”哑节点”),以指向头结点的指针为链表的头指针(为了实现上的方便, 我们采用了这种带有附加头结点的链表实现方案, 同时也为将来我们将该链表改造成循环链表与循环双链表打下了基础)。
由于单链表是一种顺序存取的结构, 因此为找第 i 个数据元素, 必须先找到第 i-1 个数据元素。
单链表节点构造:
class Node { private: Type data; //数据域:节点数据 Node *next; //指针域:下一个节点 };
但为了能够应用于MyList<Type>类, 需要对其改造:
//链表节点 template <typename Type> class Node { //可以将MyList类作为Node的友元 //或者将Node类做成MyList的嵌套类, 嵌套在MyList中, 也可以完成该功能 friend class MyList<Type>; template <typename T> friend ostream &operator<<(ostream &os, const MyList<T> &list); private: //constructor说明: //next = NULL; //因为这是一个新生成的节点, 因此下一个节点为空 Node(const Type &dataValue):data(dataValue), next(NULL) {} Type data; //数据域:节点数据 Node *next; //指针域:下一个节点 };
单链表构造:
//链表 template <typename Type> class MyList { template <typename T> friend ostream &operator<<(ostream &os, const MyList<T> &list); public: MyList(); ~MyList(); //将元素插入表头 void insertFront(const Type &data); //将元素插入到位置index上(index从1开始) void insert(const Type &data, int index); //删除表中所有值为data的节点 void remove(const Type &data); bool isEmpty() const; //链表反转 void invort(); //将链表(list)链接到本条链表的末尾 void concatenate(const MyList<Type> &list); private: //指向第一个节点的指针 Node<Type> *first; };
//链表的构造 template <typename Type> MyList<Type>::MyList() { //first指向一个空节点 first = new Node<Type>(0); first -> next = NULL; } //链表的析构 template <typename Type> MyList<Type>::~MyList() { Node<Type> *deleteNode = NULL; while (first != NULL) { deleteNode = first; first = first -> next; delete deleteNode; } }
元素插入:
由前面的图像可见,在单链表中插入结点只需要修改指针。但同时,若要在第i个结点之前插入元素,修改的是第i-1 个结点的指针。
因此,在单链表中第 i 个结点要进行的基本工作为:找到线性表中第i-1个结点,然后修改其指向后继的指针。
template <typename Type> void MyList<Type>::insertFront(const Type &data) { Node<Type> *newNode = new Node<Type>(data); newNode -> next = first -> next; first -> next = newNode; }
template <typename Type> void MyList<Type>::insert(const Type &data, int index) { //由于我们在表头添加了一个空节点 //因此如果链表为空, 或者在链表为1的位置添加元素 //其操作与在其他位置添加元素相同 int count = 1; //此时searchNode肯定不为NULL Node<Type> *searchNode = first; // 找到要插入的位置 // 如果所给index过大(超过了链表的长度) // 则将该元素插入到链表表尾 // 原因是 searchNode->next != NULL 这个条件已经不满足了 // 已经到达表尾 while (count < index && searchNode->next != NULL) { ++ count; searchNode = searchNode->next; } // 插入链表 Node<Type> *newNode = new Node<Type>(data); newNode->next = searchNode->next; searchNode->next = newNode; }
元素的删除:
在单链表中删除第 i 个结点的基本操作为:找到线性表中第i-1个结点,修改其指向后继的指针。
template <typename Type> void MyList<Type>::remove(const Type &data) { if (isEmpty()) return ; Node<Type> *previous = first; //保存要删除节点的前一个节点 for (Node<Type> *searchNode = first->next; searchNode != NULL; searchNode = searchNode->next) { if (searchNode->data == data) { previous->next = searchNode->next; delete searchNode; //重新调整searchNode指针 //继续遍历链表查看是否还有相等元素 //如果当前searchNode已经到达了最后一个节点 //也就是searchNode->next已经等于NULL了, 则下面这条语句不能执行 if (previous->next == NULL) break; searchNode = previous->next; } previous = searchNode; } }
//链表的判空 template <typename Type> bool MyList<Type>::isEmpty() const { return first->next == NULL; }