线性表之单向链表的基本操作实现
Posted 李雷还是要学英语
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线性表之单向链表的基本操作实现相关的知识,希望对你有一定的参考价值。
线性表之单向链表的基本操作实现
这篇文章用来回顾单向链表的相关知识并实现如下几个操作:
- 初始化
- 插入
- 删除
- 逆置
- 销毁
且实现的链表为带头节点的单线非循环链表.由于链表在尾插入时需要遍历到链表的最后一个位置,所以我需要记住最后一个节点的位置,为此,本文会简单的实现一个单向链表的类.
0.链表的定义与类中提供的API
1 typedef int ELEMENT; 2 3 struct Node_s { 4 Node_s():data(),NEXT(nullptr){} 5 ELEMENT data; 6 Node_s * NEXT; 7 }; 8 9 typedef Node_s Node; 10 typedef Node * NodePtr;
1 class CForward_List 2 { 3 public: 4 CForward_List(void); 5 6 ~CForward_List(void); 7 8 // 当空时返回真 9 bool isEmpty(); 10 11 // 从文件中读入节点数据 12 size_t readFromFile(std::string &_filename); 13 14 // 在头节点后插入一个节点 15 void insert_atHead(ELEMENT &_data); 16 17 // 在尾节点后插入一个节点 18 void insert_atTail(ELEMENT &_data); 19 20 // 在指定位置后插入一个节点 21 void insert_before(ELEMENT &_positionData,ELEMENT &_insertData); 22 23 // 删除链表中指定元素的值 24 void deleteNode_single(ELEMENT &_data); 25 26 // 销毁链表中的所有数据 27 void deleteAllData(); 28 29 // 更新链表中的值 30 NodePtr update(ELEMENT &_originalData,ELEMENT &_data); 31 32 // 搜索链表中的值的位置 33 NodePtr search(ELEMENT &_data); 34 35 // 将链表逆置 36 NodePtr reverse(); 37 38 // Sort by bubble/insert/select 39 NodePtr sort(SORTWAY _sortway); 40 41 // Show 42 void showListInfo(); 43 44 private: 45 // 节点数量 46 size_t size; 47 48 // 头节点 49 NodePtr listHeadPtr; 50 51 // 尾节点 52 NodePtr listEndPtr; 53 54 // 在一个指定位置后插入一个节点 55 void insert_after(NodePtr insertPos,ELEMENT &_data); 56 57 // 删除指定节点后的节点 58 void deleteNode_specific(NodePtr beforeDeleteNode); 59 60 // 下一个是否与当前的相等 61 bool nextEqualThis(NodePtr nodePos,ELEMENT &_data); 62 63 // 冒泡排序 64 NodePtr sort_bubble(); 65 void sort_swap(ELEMENT * _data1,ELEMENT *_data2); 66 67 // 选择排序 68 NodePtr sort_select(); 69 NodePtr sort_select_min(NodePtr _startNode); 70 71 // 插入排序 72 NodePtr sort_insert(); 73 NodePtr findInsertPos(NodePtr _newListHeadPtr,NodePtr _seekedNode); 74 void sort_insert_after(NodePtr _firstNode,NodePtr _secondNode); 75 };
下面会分步的实现前面提到的函数.
1.初始化(构造)与析构函数
初始化函数先创建一个头结点,将链表的大小置为0,指向尾部的节点为空;
1 CForward_List::CForward_List(void):listHeadPtr(nullptr),listEndPtr(nullptr) 2 { 3 size = 0; 4 listHeadPtr = new Node; 5 listEndPtr = nullptr; 6 }
析构函数为了确保所有节点都被清理会调用销毁相关的函数,在deleteAllData()函数内部会处理size的值.
1 CForward_List::~CForward_List(void) 2 { 3 // 删除所有节点信息 4 deleteAllData(); 5 6 if(!listHeadPtr) 7 delete listHeadPtr; 8 if (!listEndPtr) 9 delete listEndPtr; 10 }
2.插入操作
头插入:
其实头插入就是在头指针后插入一个节点,这里体会到了使用头结点的方便性.
1 void CForward_List::insert_atHead(ELEMENT &_data){ 2 insert_after(listHeadPtr,_data); 3 }
尾插入:
其实为插入也就是在尾指针后进行插入一个节点的行为,但是这里需要判断链表是否为空,
因为,若链表为空的话尾指针指向的对象是空的,所以需要在头结点后插入,然后将尾指针指向插入的元素即可.
否则,直接在尾指针后插入即可.
1 void CForward_List::insert_atTail(ELEMENT &_data){ 2 if (isEmpty()) 3 insert_after(listHeadPtr,_data); 4 else 5 insert_after(listEndPtr,_data); 6 }
在指定位置之前插入:
对链表进行指定元素的插入操作最重要的就是,要找到指定元素的前驱节点.然后再调用前面的 insert_after 函数即可.
1 void CForward_List::insert_before(ELEMENT &_positionData,ELEMENT &_insertData){ 2 NodePtr node = listHeadPtr; 3 while (node){ 4 if (nextEqualThis(node,_positionData)){ 5 insert_after(node,_insertData); 6 break; 7 } 8 node = node->NEXT; 9 } 10 }
nextEqualThis函数是判断传入节点node与node->next的data值是否相等,相等返回true,否则返回false;
插入操作的总结:
其实插入操作就是一个遍历的过程,要找到插入位置之前的节点是关键;
这里对两个功能进行模块化,一是在指定节点后的插入行为,二是判断当前节点是否与下一个节点相等.
具体的插入操作:
1 void CForward_List::insert_after(NodePtr _insertPos,ELEMENT &_data){ 2 NodePtr insertNode = new Node; 3 insertNode->data = _data; 4 // 判断是否要移动尾指针的位置 5 if (!_insertPos->NEXT) 6 listEndPtr = insertNode; 7 // 进行插入操作 8 insertNode->NEXT = _insertPos->NEXT; 9 _insertPos->NEXT = insertNode; 10 size++; 11 }
1 bool CForward_List::nextEqualThis(NodePtr _nodePos,ELEMENT &_data){ 2 if (!_nodePos->NEXT) 3 return false; 4 else 5 return _nodePos->NEXT->data == _data; 6 }
这里的代码和许多教科书上的代码都不尽相同,因为我想着什么是插入操作的共同点,然后将其提取出来,将代码量减少.
但是上面有一个问题就是在指定位置插入的过程中,遍历查找的操作会频繁调用nextEqualThis函数,要论如何优化的话我想要是设置为inline可能会是一个不错的选择,但是编译其听不听我的话就是另一回事了:)
3.删除操作
删除操作和前面的在指定位置插入有许多相同之处.,也是遍历以查找到删除之前的那个节点,然后在那个位置上执行删除操作.
1 void CForward_List::deleteNode_single(ELEMENT &_data){ 2 NodePtr node = listHeadPtr; 3 while (node){ 4 if (nextEqualThis(node,_data)){ 5 deleteNode_specific(node); 6 break; 7 } 8 node = node->NEXT; 9 } 10 }
具体的删除操作:
1 void CForward_List::deleteNode_specific(NodePtr _beforeDeleteNode){ 2 NodePtr deletedNode = _beforeDeleteNode->NEXT; 3 _beforeDeleteNode->NEXT = deletedNode->NEXT; 4 // 若删除的节点是尾节点,则需要重新设置尾节点 5 if (!deletedNode->NEXT) 6 listEndPtr = _beforeDeleteNode; 7 size--; 8 delete deletedNode; 9 }
删除操作的总结:
可以发现删除操作的思路和插入的大致相同,遍历是前提.但是在删除的具体操作中,需要判断是否删除的是尾指针.
4.逆置操作
逆置的思路就是遍历的过程中把当前这个节点指向前面的节点. 10 - 19 行才是关键,只是在遍历的过程中需要几个临时变量记住即将发生变化的节点.
1 NodePtr CForward_List::reverse(){ 2 NodePtr 3 head = listHeadPtr->NEXT, 4 tmpPtr = nullptr, 5 preNode = nullptr; 6 7 // Record the end_pointer 8 listEndPtr = head; 9 10 while (head){ 11 // Record the next node,beacuse I will change the next value 12 tmpPtr= head->NEXT; 13 // Reverse operation 14 head->NEXT = preNode; 15 // Record current node 16 preNode = head; 17 // Require the next node. 18 head = tmpPtr; 19 } 20 // Make the head_pointer point to the reversed list. 21 listHeadPtr ->NEXT = preNode; 22 23 return listHeadPtr; 24 }
5.排序操作
这里实现了三种排序方式,根据参数的不同做出不同的反映.可惜的是他们的平均时间复杂度都是O(n^2)
1 NodePtr CForward_List::sort(SORTWAY _sortway){ 2 if(_sortway == BUBBLESORT) 3 listHeadPtr = sort_bubble(); 4 else if (_sortway == SELECTSORT) 5 listHeadPtr = sort_select(); 6 else if (_sortway == INSERTSORT) 7 listHeadPtr = sort_insert(); 8 return listHeadPtr; 9 }
冒泡排序:
按照从大到小排序.
1 NodePtr CForward_List::sort_bubble(){ 2 for (NodePtr kNode = listHeadPtr->NEXT;kNode;kNode = kNode->NEXT) 3 for (NodePtr fNode = listHeadPtr->NEXT;fNode;fNode=fNode->NEXT) 4 if (kNode->data > fNode->data) 5 sort_swap(&kNode->data,&fNode->data); 6 return listHeadPtr; 7 }
选择排序:
按照从小到大排序.遍历到的每个元素都是最小的元素.
1 NodePtr CForward_List::sort_select(){ 2 for (NodePtr kNode = listHeadPtr->NEXT;kNode;kNode = kNode->NEXT){ 3 NodePtr minNodePtr = sort_select_min(kNode); 4 sort_swap(&kNode->data,&minNodePtr->data); 5 } 6 return listHeadPtr; 7 }
选择最小元素的具体过程;这里有一个细节,就是为什么不在sort_select_min函数传入kNode->NEXT,因为避免返回值是nullptr.导致sort_swap异常.
1 NodePtr CForward_List::sort_select_min(NodePtr _startNode){ 2 ELEMENT minData = _startNode->data; 3 NodePtr minPtr = _startNode; 4 for (NodePtr fNode = _startNode->NEXT;fNode;fNode=fNode->NEXT){ 5 if (fNode->data < minData){ 6 minData = fNode->data; 7 minPtr = fNode; 8 } 9 } 10 return minPtr; 11 }
插入排序:
按照从小到大排序.这里实现的不是就地的插入排序.而是新建立一个和拥有相同头结点的链表,不过并不会涉及节点的创建,只是节点之间的"再连接";
1 NodePtr CForward_List::sort_insert(){ 2 NodePtr 3 kNode = listHeadPtr->NEXT, 4 newListHeadPtr = listHeadPtr; 5 newListHeadPtr->NEXT = nullptr; 6 7 NodePtr tmp; 8 while (kNode){ 9 // 找到在新链表中可插入的位置 10 NodePtr insertPos = findInsertPos(newListHeadPtr,kNode); 11 // 临时保存下一个节点的位置 12 tmp = kNode->NEXT; 13 // 在新链表中进行插入操作 14 sort_insert_after(insertPos,kNode); 15 // 获取临时保存的下一个节点 16 kNode = tmp; 17 } 18 19 return newListHeadPtr; 20 }
插入排序的具体过程,保存怎么寻找插入的位置,以及在指定位置后进行插入操作.
1 NodePtr CForward_List::findInsertPos(NodePtr _newListHeadPtr,NodePtr _seekedNode){ 2 NodePtr kNode; 3 for (kNode = _newListHeadPtr;kNode->NEXT;kNode = kNode->NEXT){ 4 if (_seekedNode->data < kNode->NEXT->data) 5 return kNode; 6 } 7 return kNode; 8 } 9 void CForward_List:: sort_insert_after(NodePtr _insertPos,NodePtr _insertNode){ 10 if (!_insertPos->NEXT) 11 listEndPtr = _insertNode; 12 _insertNode->NEXT = _insertPos->NEXT; 13 _insertPos->NEXT = _insertNode; 14 }
交换两个节点元素:
1 void CForward_List::sort_swap(ELEMENT * _data1,ELEMENT *_data2){ 2 ELEMENT tmp; 3 tmp = *_data1; 4 *_data1 = *_data2; 5 *_data2 = tmp; 6 }
排序操作总结:
以上的排序实现都是仅仅交换节点元素,并没有实现节点的"真"交换.即连带删除/插入的操作,我认为得看具体实现要求,目前这已经能够实现链表的排序了.
6.销毁操作
这里说明一下为什么要判断size是否等于0,因为我认为若多次链表的操作之后,在进行链表的销毁肯定会size==0的,但是留一个悬念,万一之前的操作哪里有问题(new 失败...),则会导致清理完后size不等于0.
所以留着残余的数据以便查找问题所在.
1 void CForward_List::deleteAllData(){ 2 std::cout << "deleting all node "<<std::endl; 3 NodePtr node = listHeadPtr->NEXT,nextNode; 4 while (node) { 5 nextNode = node->NEXT; 6 delete node; 7 node = nullptr; 8 size--; 9 node = nextNode; 10 } 11 if(size == 0){ 12 listHeadPtr->NEXT = nullptr; 13 listEndPtr = nullptr; 14 } 15 }
好了,这些就是基本的链表操作,相关的代码可以在 我的github上参考源码.
以上是关于线性表之单向链表的基本操作实现的主要内容,如果未能解决你的问题,请参考以下文章