首页 > 其他分享 >数据结构——线性表2(链表)

数据结构——线性表2(链表)

时间:2024-03-12 21:32:41浏览次数:17  
标签:head curr 线性表 next 链表 ListNode 数据结构 节点

【基本知识】

链表是一种常见的数据结构,用于存储和组织数据。它由一系列节点组成,每个节点包含两部分:数据(data)和指向下一个节点的指针(*next)。链表中的节点可以在内存中不连续地分布,通过指针将它们连接起来。

链表有多种类型,其中最常见的是单向链表和双向链表。在单向链表中,每个节点只有一个指针指向下一个节点;而在双向链表中,每个节点有两个指针,分别指向前一个节点和后一个节点。

链表的优点是插入和删除操作的效率较高,因为只需要修改指针的指向即可,而不需要移动其他元素。然而,链表的缺点是访问特定位置的元素时效率较低,需要从头节点开始遍历。和顺序表(数组)相比,链表插入元素的时间复杂度为O(1),访问特定位置元素的时间复杂度为O(n)。

数组链表
插入元素O(n)O(1)
访问特定元素O(1)O(n)

作为数据结构的一种,链表的常用操作同样包括增,删,改,查,如下:

        1.增:即插入新节点,输入新元素进入链表;

        2.删:从链表中删除指定位置的节点;

        3.改:可以修改链表中某个节点的值

        4.查:可以根据节点的值或索引查找链表中的节点

【代码解析】

创建节点:

每个节点中需要包含数据(data)和指向下一个节点的指针(*next)两部分;创建节点,将节点初始化,将输入值x传入节点的data。

宏定义一个元素类型eleType,便于更改链表的类型。

#include <iostream>

#define eleType int

using namespace std;

struct ListNode {
	eleType data;
	ListNode* next;

	ListNode(eleType x): data(x),next(NULL) {} //创建链表节点;初始化链表,把x赋值给data
};

创建链表:

创建一个LinkedList类,其中私有部分定义出链表的头节点head和链表空间大小size;共有部分声明一些需要使用到的函数。

class LinkedList {
private:
	ListNode* head;
	eleType size; //单向链表空间大小

public:
	LinkedList():head(NULL),size(0) {}		//链表创建函数
	~LinkedList();                          //创建析构函数删除缓存
	void insert(int i, eleType val);		//插入链表函数
	void remove(int i);						//删除链表节点函数
	ListNode* find(eleType val);			//查找函数
	ListNode* get(int i);					//获取链表
	void update(int i, eleType val);		//更新链表
	void print();							//打印链表

};

插入新节点:

定义插入节点函数,类型为空(void),包括元素索引(i)、元素数据Validation(val);

首先判断插入的节点位置是否合法,如果1.在头节点之前(即i<0) 2.超出链表内存范围(即i>size)则不合法,直接报错,在文件开头引用新的头文件

#include <stdexcept>

<stdexcept>是C++标准库中的一个头文件,它定义了一些标准的异常类,这些异常类都继承自std::exception类。它提供了一些用于处理异常的工具和机制。在C++中,当程序发生错误或异常时,可以通过抛出异常来通知上层代码,然后在合适的地方进行异常处理。而<stdexcept>中定义的异常类可以用于抛出和捕获一些常见的异常情况,例如逻辑错误(std::logic_error)、运行时错误(std::runtime_error)等。这些异常类可以帮助开发人员更好地识别和处理各种异常情况,从而提高程序的可靠性和健壮性。

若位置合法,则生成新节点:用new关键字新建一个实例对象ListNode,用于储存新插入的元素;

将插入位置分情况讨论:

1.插入位置在开头节点,则将head的值赋给newNode指向的next,将新元素数值赋给head(通俗来说,newNode->next就是新节点的下一节点,把原本头位置0号位的元素放到新插入的元素后一个位置,即1号位,再把新元素放到0号位)

2.插入位置不在开头节点时,也是同样的道理;这里我们需要一个游离指针curr来临时存放元素,类似于C++中a,b交换值时定义的第三变量temp;游离指针指向头节点的位置(便于后续遍历的操作)

先把curr的位置指向想插入节点的前一位置,将curr->next赋值给newNode->next(也就是把需要插入元素的位置的节点的值赋给newNode的下一个节点,需要插入位置的节点就是游离指针的下一个位置,即curr->next,与newNode是同一个位置),总的来说就是把要插入位置的节点的后续节点全部向后移动一位

举个例子:比如你想在三号位置处插入一个新的节点,则原先三号位和 后续的节点需要全部向后移动一位。先将游离节点遍历到二号位,它的next指针则指向三号位,由于原本的三号位置需要向后移动一位,则把它的值传给newNode的下一位,即newNode->next(此时指向四号位),将新的元素newNode赋给curr的next,即curr->next(此时指向三号位)。

最后,由于插入了新的节点,则链表的内存空间需要加一。

void LinkedList::insert(int i, eleType val) {
	if (i<0 || i>size) {
		throw std::out_of_range("Invalid position");	//小于0或超出内存范围报错
	}
	ListNode* newNode = new ListNode(val);				//生成新节点储存插入元素
	if (i == 0) 
       {										//插入头节点
		newNode->next = head;							//头节点的值赋给新节点的下一位置
		head = newNode;									//将新元素数值赋给head
	   }
	else 
       {
		ListNode* curr = head;							//定义游标节点,从head开始遍历
                    
                            //遍历i-1次,使curr等于后继next,即curr为插入结点的前一个位置
		for (int j = 0; j < i - 1; j++)
		{ curr = curr->next; }	
                            //遍历i-1次,使curr等于后继next,即curr为插入结点的前一个位置
		newNode->next = curr->next;
		curr->next = newNode;    
		
	   }
	++size;												//储存空间加一
	}

删除节点:

和插入节点相同,首先先检查其位置合法性,若合法才进行后续的删除操作;

同样分开讨论:

1.删除头位置的节点时,先创建一个temp指针存放head,head的下一节点head->next将值传给head,相当于向前移一位,最后删除存储在temp中原来的head数据;

2.删除其他位置的节点时,同样需要创建一个游离指针来存放缓存数据,将其遍历到需要删除节点的前一位置,因为我们需要找到要移除节点的前一个节点,然后创建一个指针 temp,指向要移除的节点 ,即curr->next,将 curr 的 next 指针指向 temp 的下一个节点,从而跳过要移除的节点。最后,通过 delete 关键字释放临时指针 temp 指向的内存,从而删除要移除的节点。

void LinkedList::remove(int i) {
	if (i<0 || i>=size) {
		throw std::out_of_range("Invalid position");	//小于0或超出内存范围报错
	}
	if (i == 0) {
		ListNode* temp = head;
		head = head->next;
		delete temp;
	}
	else{
		ListNode* curr = head;
		for (int j = 0; j < i -1; j++)
		{
			curr = curr->next;
		}
			ListNode* temp = curr->next;
			curr->next=temp->next;
			delete temp;
	}
	--size;
}

查找元素:

这段代码用于查找链表中包含特定值的节点:

首先创建一个指向链表头节点的指针 curr,用于遍历链表。

遍历链表,循环的条件是 curr 不为空且当前节点的值不等于要查找的值 val;换句话说就是直到找到包含特定值的节点或者遍历到链表的末尾才会停止循环。在每次迭代中,通过 curr->next 将 curr 移动到链表中的下一个节点。

循环结束后,函数返回指针 curr。如果找到了包含特定值的节点,则返回指向该节点的指针;如果没有找到匹配的节点,返回空指针。

ListNode* LinkedList::find(eleType val) {
	ListNode* curr = head;
	while (curr && curr->data != val) {
		curr = curr->next;
	}
	return curr;
}

这段代码用于获取链表中指定位置的节点元素值:

由于要接受一个索引 i,则需要判断其合法性,是否小于 0 或大于等于链表的大小 size。如果是,它会抛出一个异常,表示位置无效

和前面一样,定义一个游离指针curr,使用循环将指针 curr 移动到指定位置的节点,在每次迭代中,通过 curr->next 将 curr 移动到链表中的下一个节点。直到移动到所查找的节点时停止循环,返回该节点的值。

ListNode* LinkedList::get(int i) {
	if (i < 0 || i >= size) {
		throw std::out_of_range("Invalid position");	//小于0或超出内存范围报错
	}
	ListNode* curr = head;
	for (int j=0 ; j < i; j++)
	{
		curr = curr->next;
	}
	return curr;
}

修改元素:

由于前面已经创建了get(i)函数,这里可以直接调用;查找出该节点,将更新的值val赋值给get()返回的curr(指向当前位置i)的data。

void LinkedList::update(int i, eleType val)
{
	get(i)->data = val;
}

遍历链表并打印:

先创建一个指向链表头节点的指针 curr,用于遍历链表,当 curr 不为空,即还有节点时重复遍历。

在循环的每次迭代中,使用 cout 对象打印当前节点 curr 的值 curr->data,并在值后面加上一个空格。然后,通过 curr->next 将 curr 移动到链表中的下一个节点。

void LinkedList::print() {
	ListNode* curr = head;
	while (curr) {
		cout << curr->data << " ";
		curr = curr->next;
	}
	cout << endl;
}

【运行结果】

在main函数中创建一个名为list的链表,插入若干节点,打印链表;

将3节点更新为100,打印;

删除2节点,打印。

int main() {
	LinkedList list;		//创建一个链表名称为list
	list.insert(0, 0);
	list.insert(1, 1);
	list.insert(2, 2);
	list.insert(3, 3);
	list.insert(4, 4);
    list.print();

	list.update(3, 100);
	list.print();

	list.remove(2);
	list.print();
}

【总结】

在该结构的运用中,最重要的是节点的遍历:

curr=curr -> next 相当于 i +=1 ,为向后遍历

curr -> next=curr 相当于 i -=1 ,为向前遍历

同样的,节点的向后(前)移动也是这个道理:newNode -> next = curr -> next 

最后,理解不了的地方要多画图!!!

标签:head,curr,线性表,next,链表,ListNode,数据结构,节点
From: https://blog.csdn.net/X_Universe2004/article/details/136654661

相关文章

  • 链表基础知识详解
    引言在计算机科学中,数据结构是存储、组织数据的方式。而链表,作为一种基础而强大的数据结构,因其独特的特性,在多种算法和应用场景中拥有不可替代的地位。什么是链表,为什么要使用链表链表(LinkedList)是一种线性表,但与数组不同的是,链表中的元素在内存中并不是连续放置的。每......
  • 动态链表学习笔记:查找,插入与删除
    目录情境引入:一、数据的查找1.要求:2.思路:3.程序:4.运行:二、数据的插入 1.要求:2.思路: 3.程序: 4.运行:三、数据的删除1.要求:2.思路:3.程序:4.运行四、调整与小结:优化:运行情境引入:        学习了动态链表的输入输出后,若还需要对其进行进一步的操作,......
  • 【数据结构】排序
    文章目录一、排序的概念及引用1、排序的概念2、常见的排序算法二、常见排序算法的实现1、插入排序2、直接插入排序一、排序的概念及引用1、排序的概念排序:所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。稳定性:假定在待排......
  • 【数据结构】堆
    目录1、树的概念及结构1.1树的概念1.2树的相关概念1.3树的结构定义2、二叉树的概念及结构2.1二叉树的概念2.2特殊的二叉树2.3二叉树的性质3、堆的概念及结构3.1二叉树的存储方式3.1.1顺序存储3.1.2链式存储3.2堆的概念及结构4、堆的代码实现4.1堆的初始化(建堆)......
  • 洛谷题单指南-线性表-P4387 【深基15.习9】验证栈序列
    原题链接:https://www.luogu.com.cn/problem/P4387题意解读:判断一组序列入栈后,出栈序列的合法性。解题思路:数据长度100000,直接模拟堆栈的入栈和出栈即可遍历每一个入栈元素,依次入栈,每一个元素入栈后,比较栈顶元素和出栈序列第一个,如果相等,则出栈,持续进行比较、出栈直到不相等......
  • 【算法】【线性表】【数组】在排序数组中查找元素的第一个和最后一个位置
    1 题目给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。如果数组中不存在目标值 target,返回 [-1,-1]。你必须设计并实现时间复杂度为 O(logn) 的算法解决此问题。示例1:输入:nums=[5,7,7,8,8,......
  • leetcode160.链表相交
    160.相交链表 给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。图示两个链表在节点 c1 开始相交:题目数据 保证 整个链式结构中不存在环。注意,函数返回结果后,链表必须 保持其原始结......
  • Leetcode.19. 删除链表的倒数第 N 个结点
    给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。示例1:输入:head=[1,2,3,4,5],n=2输出:[1,2,3,5]示例2:输入:head=[1],n=1输出:[]示例3:输入:head=[1,2],n=1输出:[1] 提示:链表中结点的数目为 sz1<=sz<=300<=Node.val<=100......
  • 洛谷题单指南-线性表-P1241 括号序列
    原题链接:https://www.luogu.com.cn/problem/P1241题意解读:括号配对问题,直观上是堆栈的应用。关键的匹配策略是读懂该句:考察它与它左侧离它最近的未匹配的的左括号。解题思路:本题所需核心数据结构是堆栈,由于要实现从栈顶找到第一个未匹配的左括号,所以堆栈中只存所有左括号。从......
  • “c语言+结构体+链表”实现名片系统
    //名片系统//第一步:创建名片姓名:年龄:(23)手机号:(默认为171****3422)地址:河南洛阳// 公司:tzh职务:学员//输出名片信息////第二步:删除已存在的名片////第三步:修改信息#可指定修改内容////第四步:查询信息#可查询相关姓名对应的信......