Day2:链表

一、基本概念

链表:
    两种线性结构之一
    
单链表 双链表 循环单链表 循环双链表
有头  无头   

增删改查


pHead 传参给 head  单向值传递 
函数中修改head 没有影响 pHead

 1.注:

添加链表数据时,一个要判断链表本身是否为NULL,一个要判断链表里面是否有数据(即头结点是否为空)

2.当pMove为空的时候,再进来判断pMove->data访问中断!!!!

  solution:(利用短路性质,将后面的条件提前) 3.有关C语言传参的性质:(为何此处的要写二级指针!!!)

 发现,头结点肯定是会产生一个拷贝本的,但是指针域存放的地址是拷贝过来了,所以可以正常变动后面的(同时修改原件),修改头head的时候,需要传入二级指针。

 有关拷贝头节点后,其指针域的值(即除了头部是拷贝本,其余位置均是原链表的部分->相当于产生了两个头结点!!)----->>>>>下面为例证代码

#include<cstdio>
#include<assert.h>
#include<stdlib.h>
struct Node
{
	int data;
	struct Node* Next;
};

struct Node* initHead()
{
	struct Node* t = (struct Node*)malloc(sizeof(struct Node));
	assert(t);
	t->Next = NULL;
	return t;
}
struct Node* createNode(int data)
{
	struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
	assert(newNode);
	newNode->data = data;
	newNode->Next = NULL;
	return newNode;
}
void print(struct Node* list)
{
	printf("      头结点的指针域为:%p", &list->Next);
}
void push_back(struct Node* list, int data)
{
	struct Node* pNew = createNode(data);
	if (pNew == NULL)return;
	struct Node* pMove = list;
	while (pMove->Next != NULL)
	{
		pMove = pMove->Next;
	}
	pMove->Next = pNew;
}
int main()
{
	struct Node* list = initHead();
	push_back(list, 555);
	printf("main中的head的指针域为:%p\n", &list->Next);
	print(list);
	return 0;
}

输出: 

main中的head的指针域为:00000131EB6FF148
            头结点的指针域为:00000131EB6FF148

4.删除某元素第一次出现的结点:勿忘记头结点要单独考虑

二、用C语言的单链表两种实现(封装写法&二级指针写法)

#include <stdio.h>

#define IS_LIST  1


//链表节点类型
struct Node
{
	int				data;
	struct Node*	Next;
};
#if IS_LIST
//链表类型
struct List
{
	struct Node*	pRoot;
};
#endif

#if IS_LIST
//初始化链表
void initList(struct List* list);
#endif

//创建链表节点并返回
struct Node* createNode(int newData);

//增
#if IS_LIST
//尾插法  push_back
void push_back(struct List* pList, int push_backData);
#else
void push_back(struct Node** head, int push_backData);
#endif


#if IS_LIST
//头插法  push_front
void push_front(struct List* pList, int push_backData);
#else
void push_front(struct Node** head, int push_backData);
#endif

#if IS_LIST
//中间插入  insert   新节点插入到pos位置的后面
void insertNode(struct List* pList,int pos,int insertNodeData);
#else
void insertNode(struct Node** head, int pos, int insertNodeData);
#endif

//从head链表中找到pos节点并返回,找不到 返回NULL
struct Node* findPos(struct Node* head,int pos);


#if IS_LIST
void travel(struct List* pList);
#else
void travel(struct Node* head);
#endif
//从 head链表中找到 findData 并修改为 changeData 如果找不到 直接结束
void changeNodeData(struct Node** head, int findData, int changeData);

//删除
//删除链表中第一个数据为deleteData的节点
void deleteNodeData(struct Node** head, int deleteData);
//删除链表中第pos个节点
void deleteNodePos(struct Node** head, int pos);

//删除链表中第一个节点
void deleteHead(struct Node** head);

int main()
{
	return 0;
}

//删除
//删除链表中第一个数据为deleteData的节点
void deleteNodeData(struct Node** head, int deleteData)
{
	if (head == NULL || *head == NULL)return;
	struct Node* pMove = *head;
	struct Node* pre = pMove;
	while (pMove != NULL && pMove->data != deleteData)  /*注意顺序!!!*/
	{
		pre = pMove;
		pMove = pMove->Next;
	}
	if (pMove == NULL)return;
	else if (pMove == *head)
	{
		deleteHead(head);			//删除的时候,头结点要单独考虑
	}
	else/*正常找到了,pMove指向的即为待del的node*/
	{
		struct Node* pDel = pMove;//存一下需要删除的结点
		pre->Next = pMove->Next;
		free(pDel);
		pDel = NULL;
	}
}

//删除链表中第一个节点
void deleteHead(struct Node** head)
{
	if (NULL == head) return;
	//1 临时存储要删的节点
	struct Node* pDel = *head;
	//2 *head 的下一个节点要成为新的头节点
	*head = (*head)->Next;
	//3 释放内存
	free(pDel);
	pDel = NULL;
}
//删除链表中第pos个节点
void deleteNodePos(struct Node** head, int pos)
{
	if (NULL == head || pos < 0) return;
	if (0 == pos)
	{
		deleteHead(head);
		return;
	}
	
	//2 临时存储pos节点地址
	struct Node* pDel = findPos(*head, pos);
	if (NULL == pDel) return;
	//1 先找到下标为pos-1节点
	struct Node* pDelPrev = findPos(*head, pos - 1);
	if (NULL == pDelPrev) return;
	//3 pos-1节点的next指针指向pos的下一个节点
	pDelPrev->Next = pDel->Next;
	//4 释放pos节点内存
	free(pDel);
}

//从 head链表中找到 findData 并修改为 changeData 如果找不到 直接结束
void changeNodeData(struct Node** head, int findData, int changeData)
{
	//作业3
	/*一点思考:新产生了一个链表头(但是指针域仍然不变)!
	此处应该用到二级指针,若不用(头部)会产生拷贝本,是无法改动头部的元素的,
		注:指针域是拷贝过来了,依旧能得到目标地址(故能改到除了头部节点的元素)*/
	if (head == NULL||*head==NULL)return;
	struct Node* pMove = *head;
	while (pMove != NULL&&pMove->data != findData  )  /*注意顺序!!!*/
	{
		pMove = pMove->Next;
	}
	if (pMove == NULL)return;
	else
	{
		pMove->data = changeData;
	}
}
//从head链表中找到下标为第pos节点并返回,找不到 返回NULL
struct Node* findPos(struct Node* head, int pos)
{
	if (NULL == head) return;
	struct Node* pTemp = head;
	for (int i = 0; i < pos; i++)
	{
		if (NULL == pTemp) return NULL;
		pTemp = pTemp->Next;
	}
	return pTemp;
}

#if IS_LIST
//中间插入  insert   新节点插入到下标为pos位置(第pos+1个节点)的后面
void insertNode(struct List* pList,int pos,int Data)
{
	//作业2:
	if (pList == NULL)return ;
	if (pos == 0)
	{
		push_front(pList, Data);
		return;//勿忘直接退出
	}
	struct Node* pMove = pList->pRoot;
	struct Node* pre = pMove;
	for (int i = 0; i < pos; i++)
	{
		if (pMove == NULL)return;
		pre = pMove;/*记录前一个节点*/
		pMove = pMove->Next;/*注:若完整的执行完了所有for,出去时,pMove应该移动到了下标为pos的位置*/
	}/*pre在下标pos-1的位置*/
	
	struct Node* pNew = createNode(Data);
	if (pNew == NULL)return;
	pNew->Next = pMove;
	pre->Next = pNew;

}
#else
void insertNode(struct Node** head, int pos, int insertNodeData)
{
	if (NULL == head) return;
	if (NULL == *head || 0 == pos) 
	{
		push_front(head, insertNodeData);
		return;
	}
	struct Node* pPrev = findPos(*head, pos);//1 找到第pos+1个节点,下标为pos,为后一个前一个节点
	if (NULL == pPrev) return;
	//2 创建新节点
	struct Node* pNew = createNode(insertNodeData);
	if (pNew == NULL)return;
	//3 新节点的Next指针指向 pPrev的Next
	pNew->Next = pPrev->Next;
	//4 pPrev的Next指向新节点
	pPrev->Next = pNew;
}
#endif


#if IS_LIST
//头插法  push_front
void push_front(struct List* pList, int Data)
{
	//作业 课后去实现
	if (pList == NULL) return;
	struct Node* pNew = createNode(Data);
	if (pNew == NULL)return;
	
	pNew->Next = pList->pRoot;
	pList->pRoot = pNew;
	/*注意:本处区分push_back,无需进行对链表中有无元素进行分类,均一样!!!*/

}
#else
void push_front(struct Node** head, int Data)
{
	if (NULL == head) return;//防呆
	//1 创建新节点
	struct Node* pNew = createNode(Data);
	//2 新节点的Next指向head指向的节点
	pNew->Next = *head;
	//3 *head 被pNew赋值
	*head = pNew;
}
#endif

#if IS_LIST
void travel(struct List* pList)
{
	struct Node* pTemp = pList->pRoot;
	printf("list:");
	while (pTemp)
	{
		printf("%d ", pTemp->data);
		pTemp = pTemp->Next;
	}
	printf("\n");
}
#else
void travel(struct Node* head)
{
	struct Node* pTemp = head;
	printf("list:");
	while (pTemp){
		printf("%d ", pTemp->data);
		pTemp = pTemp->Next;
	}
	printf("\n");
}
#endif

#if IS_LIST
//尾插法  push_back
void push_back(struct List* pList, int push_backData)
{
	if (NULL == pList) return;//防呆
	//1 创建新节点
	struct Node* pNew = createNode(push_backData);
	if (NULL == pNew) return;//防呆

	if (pList->pRoot)       //->区分plist==NULL,一个整个链表是空的,一个是链表已经init但是没有任何数据,即头结点不存有其他数据
	{//如果链表不为空
		struct Node* pTemp = pList->pRoot;
		while (pTemp->Next != NULL)				//2 找到尾结点
		{
			pTemp = pTemp->Next;
		}

		//3 尾节点的Next指针指向新节点  ++
		pTemp->Next = pNew;
	}
	else
	{//如果 链表为空
		//2 pList的成员指向新节点
		pList->pRoot = pNew;
	}

}
#else
void push_back(struct Node** head, int push_backData)
{
	//1 创建新节点
	struct Node* pNew = createNode(push_backData);
	if (NULL == pNew) return;//防呆

	if (*head)
		{//如果链表不为空
		//2 找到尾节点
		struct Node* pTemp = *head;
		while (pTemp->Next != NULL){
			pTemp = pTemp->Next;
		}
		//3 尾节点的Next指针指向新节点
		pTemp->Next = pNew;
	}
	else
	{//如果 链表为空
		//2 pList的成员指向新节点
		*head = pNew;
	}
}
#endif

#if IS_LIST
//初始化链表
void initList(struct List* list)
{
	list->pRoot = NULL;
}
#endif

//创建链表节点并返回
struct Node* createNode(int newData)
{
	//开内存
	struct Node* pNew = (struct Node*)malloc(sizeof (struct Node));
	if (NULL == pNew)
	{
		printf("申请内存失败"); return NULL;
	}
	//数据赋值
	pNew->data = newData;
	pNew->Next = NULL;
	return pNew;
}

 其他写法参照:

        无头单链表的再封装写法

无头单链表的再封装写法解析https://blog.csdn.net/weixin_60569662/article/details/122878134

        无头单链表的“二级指针”写法

无头单链表的二级指针写法解析https://blog.csdn.net/weixin_60569662/article/details/123041179

三、有序链表的构建、合并、反转

数据结构 --- 有序链表的构建、合并、反转https://blog.csdn.net/weixin_60569662/article/details/124679538

注:此处传参为何是一级指针传入?   ->采用有头链表,头结点不存放数据,后面的指针域相同 (因为采用有头链表,此文的所有push_sort等操作去判断边界条件时,都会产生影响!!)

四、 双向链表

数据结构 --- c语言实现双向链表https://blog.csdn.net/weixin_60569662/article/details/125495131比较值得注意的细节:

       删除节点的时候,一定要记得删头or删尾的时候,考虑本链表是不是curSize为1,同时要处理一下,list的head和tail都需要保证是nullptr。

(简单来说:

        ①你把尾结点free,但是pre节点的next(也就是tail)需要置为空,当然这是在pre不为null的情况下

        ②如果pre为空->删完之后,没有元素了,list存放头结点的指针也需要置为null)

五、循环链表

数据结构 --- c语言实现双向循环链表https://blog.csdn.net/weixin_60569662/article/details/125737109

六、广义表

广义表的创建和操作(C语言)https://blog.csdn.net/any_ways/article/details/124053024?utm_medium=distribute.pc_aggpage_search_result.none-task-blog-2~aggregatepage~first_rank_ecpm_v1~rank_v31_ecpm-5-124053024-null-null.pc_agg_new_rank&utm_term=c%E8%AF%AD%E8%A8%80%E5%B9%BF%E4%B9%89%E8%A1%A8%E5%AE%9E%E7%8E%B0&spm=1000.2123.3001.4430

七、C++类中类写法

注意:成员函数的返回值类型为类中类类型,在类外进行实现,需要typename+类名修饰。

#pragma once
#include <iostream>
using namespace std;
template <class T>
class MyList
{
	struct Node
	{
		T		data;
		Node*	pNext;
		Node(const T& data)
		{
			this->data = data;
			pNext = NULL;
		}
	};

	Node* pHead;
public:
	MyList(){
		pHead = NULL;
	}

	void appendNode(const T& data);
	void addNode(const T& data);
	void insertNode(int pos, int insertNodeData);
	void travel();
	//删除链表中第pos个节点
	void deleteNodePos(int pos);
	//删除链表中第一个节点
	void deleteHead();
private:
	Node* _findPos(int pos);
};

template <class T>
typename MyList<T>::Node* MyList<T>::_findPos(int pos)
{
	Node* pPos = pHead;
	for (int i = 0; i < pos; i++)
	{
		if (NULL == pos) return NULL;
		pPos = pPos->pNext;
	}
	return pPos;
}
template <class T>
void MyList<T>::appendNode(const T& data)
{
	//1 创建新节点
	Node* pNew = new Node(data);
	//2 找到尾节点
	Node* pTemp = pHead;
	if (pTemp)
	{
		while (pTemp->pNext)
		{
			pTemp = pTemp->pNext;
		}
		//3 新节点插入到尾节点之后
		pTemp->pNext = pNew;
	}
	else
	{//pTemp为NULL
		pHead = pNew;
	}
	
}
template <class T>
void MyList<T>::addNode(const T& data)
{
	//1 创建新节点
	Node* pNew = new Node(data);
	//2 新节点的next指针指向原来头节点
	pNew->pNext = pHead;
	//3 新节点成为头节点
	pHead = pNew;
}
template <class T>
void MyList<T>::insertNode(int pos, int insertNodeData)
{
	if (pos < 0) return;
	if (NULL == pHead || 0 == pos)
	{
		addNode(insertNodeData);
		return;
	}
	//新建节点
	Node* pNew = new Node(insertNodeData);
	//找到pos节点
	Node* pPrev = _findPos(pos);
	//新节点成为pos节点后面的节点
	pNew->pNext = pPrev->pNext;
	pPrev->pNext = pNew;
}
template <class T>
void MyList<T>::travel()
{
	Node* pTemp = pHead;
	cout << "list:";
	while (pTemp)
	{
		cout << pTemp->data << " ";
		pTemp = pTemp->pNext;
	}
	cout << endl;
}
template <class T>
//删除链表中第pos个节点
void MyList<T>::deleteNodePos(int pos)
{
	if (NULL == pHead || pos < 0) return;
	if (0 == pos)
	{
		deleteHead();
		return;
	}
	Node* pDelPrev = _findPos(pos - 1);
	if (NULL == pDelPrev) return;
	Node* pDel = _findPos(pos);
	if (NULL == pDel) return;

	pDelPrev->pNext = pDel->pNext;
	delete pDel;
}

template <class T>
//删除链表中第一个节点
void MyList<T>::deleteHead()
{
	if (NULL == pHead) return;
	Node* pDel = pHead;
	pHead = pDel->pNext;
	delete pDel;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_Ocean__

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值