C++ Linked list: Traversing a Linked list(遍历链表)

本文详细介绍了链表遍历的基本原理和技术要点,包括如何通过遍历指针访问链表中的每个节点,以及如何正确地终止遍历过程。此外,还提供了具体的代码实现案例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

遍历链表很重要。  因为一旦可以遍历一个链表了, 我们就可以很容易的实现operations like print the data of the linkedc list,  或者将一个节点插入到某个特殊的位置处。

或者删除某个特定的节点, 修改节点的数据等等。

 

由于链表的第一个节点是由指针head指向。 所以我们可以存取第一个链表节点的数据, 语句为:

head -> Data;

上面的语句实际上做的事解参考我们的指针, 然后取出指针对应节点的数据:

即等价于:

(*head).Data; // using dot operator

不过一般采用的存取方式是head -> Data, 因为写法上比较简单。

当要存取第二个节点的Data 的时候, 我们当然可以用语句: head -> next  -> Data;

我们可以采用访问第三个节点数据, 第四个,。。。

然后当访问第n 个节点的数据的时候呀, 我们会想想最后head 之后跟着多长的链了, complex, messy。

所以这个方法不可取。

 

如果采用如下的方法是:

如上图, 500, 700 为节点的内存地址。 linked list 的节点的位置地址是不连续的, 后面的节点的内存地址可能在前者节点的前面。

如果我们采用迭代语句:

head = head -> next;

我们当然不需要链接那么的多的链去访问后面的节点数据, 我们采用了head 指针, 沿着链表向后面运动。

然而这个方法是有问题的。 因为我们如果这样移动head 指针的位置, 就会丢失掉head之前的节点无法存取, 而且产生了无法访问的没存垃圾。

 

所以我们的解决办法是, 定义一个遍历指针tp,  最开始tp初始化为head的内容, 也就是说tp 最开始执行第一个节点。 保持head不变, 这样我们就能够保证链表不会丢失了。

 tp = tp -> next;

 

这样我们就可以去遍历我们的指针了。 然而由于是循环语句, 我们还必须知道loop何时停止。 由于最后一个节点的next指针的内容为null, 所以这就是我们终止遍历的条件。

每次遍历的时候都要检查当前的node 是否能是最后的一个节点。

语句是:

while(tp != 0) {

//some node operation

tp = tp -> next; // update our traversing pointer to next node

}

 

下面实现。 (下面是工程中改动的文件, 要查看整个工程的文件, 参看上一个linked list 的学习笔记) 打开工程NewlinkedList中的ContactList.h, 添加printList()函数如下, 变为:

/*
 *ContactList.h
 *
 *   created on Jul 6, 2014
 *      Author: ***
 *
 *
 */

 #ifndef CONTACT_LIST_H
 #define CONTACT_LIST_H


 #include "Contact.h" // the first thing to do is to include the node header file

 class ContactList {
     public:
        ContactList(); // prototype for constructor
        void AddToHead(const std::string&);//reference, 避免复制, 更快, const, 所以不让修改
         void printList();

     private:
        Contact* head;
        int length;
};

 #endif /*end of CONTACT_LIST_H*/


 

 

打开文件ContactList.cpp, 实现上面添加的接口函数:

// an implementation of ContactList.h

#include "ContactList.h"
using namespace std;

ContactList::ContactList():head(0), length(0) {

}

void ContactList::AddToHead(const string& name) {
   Contact* newOne = new Contact(name);
   if(head == 0) {
      head = newOne;
   }
   else {
      newOne -> next = head;
      head = newOne;
   }
   length++;
}

void ContactList::printList() {
   Contact* tp;
   
   tp = head;
   
   while(tp != NULL) {
      cout << tp -> name << endl;
      
      tp = tp -> next;
   }
}


下面到NewContactListApp 做如下修改一验证遍历效果:

#include "ContactList.h"
using namespace std;

int main() {
   ContactList* cl1 = new ContactList;
   string name;
   while(true) {
      cout << "Enter the name of the contact, or q to quit:";
      cin >> name;
      if(name == "q")
         break;
      cl1->AddToHead(name);
   }

   cl1 -> printList();
   return 0;
}


编译运行如下:

 

可以看出输出的顺序和我们输入的顺序是相反的, 这是正确的。 因为我们是Build the list backward。

 

 

 

 

为什么会死循环class Node: # 定义双链表的节点类 def __init__(self, data): self.data = data # 节点数据 self.prev = None # 前驱指针 self.next = None # 后继指针 class DoublyLinkedList: # 双链表类 def __init__(self): self.head = Node(None) # 头节点初始化,数据为None def append(self, data): # 向链表添加新节点 new_node = Node(data) if self.head.next is None: # 如果链表为空 self.head.next = new_node # 头节点指向新节点 new_node.prev = self.head # 设置新节点的前驱指针 else: current = self.head while current.next: # 找到最后一个节点 current = current.next current.next = new_node # 将新节点连接到尾部 new_node.prev = current # 设置新节点的前驱指针 def reverse(self): # 逆置双链表 if self.head.next is None: # 如果链表为空,则直接返回 return current = self.head.next # 从头节点的下一个节点开始遍历 last = None # 用于保存逆置后的最后一个节点 while current: # 遍历链表 last = current # 当前节点将成为最后一个节点 current.prev, current.next = current.next, current.prev # 交换前驱和后继指针 current = current.prev # 移动到当前节点的前驱(最初的后继) print(current) # 更新头节点指向逆置后的新链表 self.head.next = last # 头节点指向新表头 if last: # 若链表非空 last.prev = self.head # 更新新头节点的前驱指针为头节点 # 设置新链表的每个节点的指针 current = last # 从新表头开始 while current: # 重新设置所有节点的prev和next指针,避免持有旧的指针 print( f"Node data: {current.data} - Prev: {current.prev.data if current.prev else None}, Next: {current.next.data if current.next else None}") current = current.next print(current) # 测试代码 if __name__ == "__main__": dll = DoublyLinkedList() # 创建一个双链表 # 添加节点 for i in range(1, 6): # 向链表添加数据 1-5 dll.append(i) # 打印逆置前的链表 print("List before reversing:") current = dll.head.next # 从头节点的下一个节点开始 while current: # 遍历并打印链表 print(current.data, end=" ") current = current.next
最新发布
03-24
1 Traversing a singly linked list with a dummy header 分数 20 全屏浏览 切换布局 作者 伍建全 单位 重庆科技大学 In this problem you will Implement a function to traverse a singly linked list with a dummy header and output the data field of each node. Structure description: The node structure is shown below: typedef struct ListNode { int data; struct ListNode *next; } node; typedef node* position; typedef position List; Function definition: void traversal(List L); The parameter L is a pointer to the dummy header. The function outputs the data field of each node as it traverses the singly linked list. Test program example: #include <stdio.h> #include <stdlib.h> typedef struct ListNode { int data; struct ListNode *next; }node; typedef node* position; typedef position List; void traversal(List L); // The questioner has implemented the createList function. // This function reads a series of positive integers separated by spaces // and inserts them into a linked list using the head insertion method. // Entering -1 indicates the end of input. // creatgeList函数由题目提供,不需要在本题的答案中实现 List createList(); int main(void) { List L = createList(); traversal(L); return 0; } /* Please write the answer here */ Input Specification: A series of positive integers separated by spaces. Entering -1 indicates the end of input. Output Specification: Output the data field of each node in the singly linked list. After each number, output a space.(每个整数后输出1个空格) Sample Input : 9 5 7 2 -1 Sample Output : 2 7 5 9 代码长度限制 16 KB 时间限制 400 ms 内存限制 64 MB
03-19
### 使用数组模拟链表数据结构 虽然链表和数组是两种不同的线性数据结构,各自拥有独特的优缺点,但在某些情况下我们确实可以用数组来“模拟”出类似单向链表的行为。这主要是为了简化内存管理或是利用静态数组带来的性能优化特性(例如更好的缓存命中率)。下面将详细介绍如何使用数组实现一个类似于链表的数据结构。 #### 1. **定义节点结构** 首先需要设计一种存储每个结点信息的方式。由于我们要用数组代替传统意义上的指针链接机制,则可以在数组中为每个位置预留足够的空间存放两个关键字段: - 数据域 (`data`):保存实际储存的信息; - 下一索引 (`nextIndex`):指向下一个元素所在的位置序号,如果该节点之后没有更多项则将其置为空(-1)表示结束标志。 假设我们打算创建一个整型的简易版‘链式’数组: ```cpp struct ListNode { int data; // 存储的实际数值 int nextIndex; // 指向下一项在数组里的index (-1 表示结尾) }; const int MAX_SIZE = 100; ListNode linkedList[MAX_SIZE]; int headIndex = -1; // 初始状态下头指针也设为空 int availableStartIndex = 0; // 第一次插入时可用的第一个slot ``` 这里 `MAX_SIZE` 设定了整个虚拟链表的最大容量限制,超过此数目就不能再添加新的项目了。 #### 2. **初始化与清理工作** 接下来考虑一些必要的辅助操作如清空当前列表、检查是否已满等功能。这些步骤有助于维护好内部状态,并确保后续增删查改功能能够正常运作而不引发越界访问等问题。 ```cpp void clearLinkedList() { for(int i=0;i<MAX_SIZE;++i){ linkedList[i].nextIndex=-1; // 所有的node都标记成未连接的状态 } headIndex = -1; } bool isFull(){ return (availableStartIndex >= MAX_SIZE); } ``` #### 3. **插入新节点** 当用户想要往这个伪链表里加入一条记录时,我们需要找到合适的地方安插进去,并更新相关联接关系: ```cpp bool insertAtBeginning(const int newData) { if(isFull()) return false; // 将新节点放到目前第一个可用的位置上 linkedList[availableStartIndex].data = newData; // 更新即将成为头部的新节点应该连到哪里去 linkedList[availableStartIndex].nextIndex=headIndex; // 修改全局变量使得下一轮insert动作从下一个slot开始查找空位 headIndex = availableStartIndex++; return true; } ``` 以上代码段展示了在一个基于数组构建出来的假想链表前端新增加元素的过程。请注意这里的逻辑只是示范性质,实际情况还需要根据具体应用场景调整细节处理策略。 #### 4. **遍历打印所有节点** 最后一步就是编写用于迭代展示全部内容的功能了——即使是在这样的非典型环境下也能方便地获取完整链条上的每一段信息。 ```cpp void traverseAndPrint() { printf("Traversing the pseudo-linked-list:\n"); int currentIndex = headIndex; while(currentIndex != -1){ printf("%d ",linkedList[currentIndex].data ); currentIndex = linkedList[currentIndex].nextIndex ; } putchar('\n'); } ``` 通过这种方式,我们可以较为逼真地重现经典单向链表的基本行为模式,同时保留了一定程度上的灵活性以便应对更复杂的业务逻辑需求。当然这只是最基本的入门级做法,随着理解加深还有许多改进空间可供探索! ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值