链表的概念和基础函数的接口实现与部分相关oj题题解

目录

链表的概念及结构

 基础函数的接口实现

申请节点和释放空间

尾插

头插 

尾删

头删

查找

在pos位置前插入一个新的节点

在pos位置后插一个

删除pos处前一个的节点

删除pos处后一个的节点

释放内存

单向链表的意义 

相关OJ题

1. 删除链表中等于给定值 val 的所有节点

2. 反转一个单链表

 3.链表的中间节点

 4.合并两个有序链表


链表的概念及结构

    链表是一种物理存储结构上 非连续、非顺序 的存储结构,数据元素的逻辑顺序是通过链表中的 指针链
接次序实现的 。
特点:
    按需申请空间,不用了释放空间。
    中间插入数据,不需用挪动数据
    不存在空间浪费
缺点:
    每一个数据,都要存一个指针去连接后面的数据节点
    不支持随机访问。(用下标直接访问第i个)

 基础函数的接口实现

函数的定义和链表的定义

typedef int SLTDataType;
typedef struct SListNode
{
	SLTDataType data;
	struct SListNode* next;//此处 next的类型是 SListNode* ,展开后就是data和 next。 
							//SListNode* next 连接着下一个data
}SLTNode;



void SListPrint(SLTNode* phead);
void SListPushBack(SLTNode** pphead,SLTDataType x);
void SListPushFront(SLTNode** pphead, SLTDataType x);
void SListPopBack(SLTNode** pphead);
void SListPopFront(SLTNode** pphead);

SLTNode* SListFind(SLTNode* phead, SLTDataType x);
void SListInsertAfter(SLTNode* pos, SLTDataType x);//在pos位置之后插入一个节点
void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);//在pos位置之前插入一个节点
//void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);//pos下标处插入
void SListEase(SLTNode** pphead, SLTNode* pos);	//删除
void SListEaseAfter(SLTNode* pos);
void SListDestory(SLTNode** pphead);

申请节点和释放空间

链表按需申请,每次插入新的节点时都需要申请空间。申请了内存需记得最后释放内存,防止造成内存泄露

//申请节点用
SLTNode* BuyListNode(SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		printf("内存分配失败");
		exit(-1);
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}

void SListDestory(SLTNode** pphead)
{
	assert(*pphead);
	SLTNode* cur = *pphead;
	while (cur != NULL)
	{
		SLTNode* next = cur->next;//保存下一个
		free(cur);
		cur = next;
	}
	*pphead = NULL;
}

尾插

void SListPushBack(SLTNode** pphead, SLTDataType x)
{
	SLTNode* newnode = BuyListNode(x);
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		SLTNode* tail = *pphead;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		tail->next = newnode;
	}
}

头插 

void SListPushFront(SLTNode** pphead, SLTDataType x)
{
	SLTNode* newnode = BuyListNode(x);
	//SLTNode* cur = *pphead;
	newnode->next = *pphead;//将新节点的next指向原本的第一个节点
	*pphead = newnode; 
}

尾删

void SListPopBack(SLTNode** pphead)
{
	if (*pphead == NULL)
	{
		return 0;
	}
	if ((*pphead)->next == NULL)//当链表只剩下一个的时候
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SLTNode* prev = NULL;
		SLTNode* tail = *pphead;
		while (tail->next)
		{
			prev = tail;
			tail = tail->next;
		}
		free(tail);
		tail = NULL;
		prev->next = NULL;
	}
}

头删

由于头删后,没有第二个的链接,所以需要先保存第二个的链接

void SListPopFront(SLTNode** pphead)
{
    if(*pphead == NULL)
    {
        return ;
    }
    //先保存下一个
    SLTNode* next = *pphead->next;
    free(*pphead);
    *pphead = next;
}

查找

SLTNode* SListFind(SLTNode* phead, SLTDataType x)
{
	SLTNode* cur = phead;
	while (cur)
	{
		if (cur->data == x)
		{
			return cur;
		}
		else
		{
			cur = cur->next;
		}
	}
	return NULL;
}

在pos位置前插入一个新的节点

void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)//在pos位置之前插入一个节点
{
	SLTNode* newnode = BuyListNode(x);
	if (*pphead == pos)//如果一个链接都没有
	{
		newnode->next = *pphead;
		*pphead = newnode;
	}
	else
	{
		//找到pos的前一个位置
		SLTNode* proPrev = *pphead;
		while (proPrev->next != pos)
		{
			proPrev = proPrev->next;
		}
		proPrev->next = newnode;	//将pos的前一个节点接入newnode
		newnode->next = pos;		//将newnode 接入pos
	}
}

在pos位置后插一个

void SListInsertAfter(SLTNode* pos, SLTDataType x)
{
	SLTNode* newnode = BuyListNode(x);
	newnode->next = pos->next;	//		Dpos		此处的D2.next先赋值给Dpos.next,再Dpos赋值给D1.next
	pos->next = newnode;		//  D1		  D2	如果先将Dpos赋值给D1.next,那么Dpos.next无法找到下一个节点 
						
}

删除pos处前一个的节点

void SListEase(SLTNode** pphead, SLTNode* pos)
{
	if (*pphead == pos)	//删头
	{
		//*pphead = pos->next;
		//free(pos);//或者直接调用头删
		SListPopFront(&pphead);
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
		pos = NULL;
	}
}

删除pos处后一个的节点

void SListEaseAfter(SLTNode* pos)
{
	assert(pos->next);
	SLTNode* next = pos->next;
	pos->next = next->next;	
	free(next);
}

释放内存

void SListDestory(SLTNode** pphead)
{
	assert(*pphead);
	SLTNode* cur = *pphead;
	while (cur != NULL)
	{
		SLTNode* next = cur->next;//保存下一个
		free(cur);
		cur = next;
	}
	*pphead = NULL;
}

单向链表的意义 

单纯的单链表增删查改 意义不大  。 学他的原因:
1.很多OJ题考的都是单链表
2.单链表更多的是作为 复杂链表的子结构
链表存储结构还需要看 双向链表

相关OJ题

1. 删除链表中等于给定值 val 的所有节点

力扣https://leetcode-cn.com/problems/remove-linked-list-elements/description/需要处理的地方

由于将⑥删去后,前一个失去了指向下一个的链接,故此需要一个prev,用于前一个节点去链接节点⑥的下一个链接。

图解如图所示:  当cur->val == val时

pevr需要先获取cur的下一个节点,后free(节点6)

 

struct ListNode* removeElements(struct ListNode* head, int val){
    //将 前一个位置等于它的后一个位置
    //需要将前一个保存下来,指向后一个
    struct ListNode* prev = NULL;
    struct ListNode* cur = head;
    while(cur)
    {
     if(cur->val == val)
     {
         if (cur == head)
         {
	        head = cur->next;
            free(cur);
	    	cur = head;
	    }
        else
        {
            prev->next = cur->next;
            free(cur);
            cur = prev->next;
        } 
    }
    else {
            //迭代 往后走
             prev = cur;
             cur = cur->next;
    }
    }
    return head;
}

2. 反转一个单链表

力扣https://leetcode.cn/problems/reverse-linked-list/description/此题有两种思路解法,一种是将 → 反转

 修改后

另一种则是利用头插

//1.
struct ListNode* reverseList(struct ListNode* head) {
	if (head == NULL)
	{
		return NULL;
	}
	struct ListNode* n1, * n2, * n3;
	n1 = NULL;
	n2 = head;
	n3 = head->next;

	while (n2 != NULL)
	{
		n2->next = n1;
		n1 = n2;
		n2 = n3;
		if (n3)
		{
			n3 = n3->next;
		}
	}
	return n1;
}
//思路二 头插
struct ListNode* reverseList(struct ListNode* head)
{
	ListNode* cur = head;
	ListNode* Newhead = NULL;
	while (cur)
	{
		ListNode* next = cur->next;
		//头插
		cur->next = Newhead;
		Newhead = cur;
		//迭代往后走
		cur = next;
	}
	return Newhead;
}

 3.链表的中间节点

力扣https://leetcode.cn/problems/middle-of-the-linked-list/description/双指针法。一个fast 走两步,一个slow走一步

当链表元素个数为奇数时,fast走完,slow刚好在链表中间

当链表元素个数为偶数时,fast走到空,slow刚好在链表中间的第二个元素

若需要返回倒数第K个节点,便让fast先走K步,随后一起走,slow和fast走相同的步数

struct ListNode* middleNode(struct ListNode* head){
	struct ListNode* fast, * slow;
	fast = slow = head;
	while (fast && fast->next)
	{
		slow = slow->next;
		fast = fast->next->next;
	}
	return slow;
}

 4.合并两个有序链表

以此比较链表中的节点,每次取较小的节点尾插到新链表即可

或者使用哨兵位

struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
	//以此比较链表中的节点,每次取较小的节点尾插到新链表即可
	if (list1 == NULL) {
		return list2;
	}
	if (list2 == NULL) {
		return list1;
	}
	struct ListNode* head = NULL, * tail = NULL;
	while (list1 && list2)
	{
		if (list1->val < list2->val)
		{
			if (head == NULL) {
				head = tail = list1;
			}
			else {
				tail->next = list1;
				tail = list1;
			}
			list1 = list1->next;
		}else{
			if (head == NULL) {
				head = tail = list2;
			}
			else {
				tail->next = list2;
				tail = list2;
			}
			list2 = list2->next;
		}
	}
	if (list1){
		tail->next = list1;
	}
	if (list2){
		tail->next = list2;
	}
	return head;
}
//第二种 哨兵位
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
	//以此比较链表中的节点,每次取较小的节点尾插到新链表即可
	if (list1 == NULL) {
		return list2;
	}
	if (list2 == NULL) {
		return list1;
	}
	struct ListNode* head = NULL, * tail = NULL;
	//哨兵位
	head = tail = (struct ListNode*)malloc(sizeof(struct ListNode));
	while (list1 && list2)
	{
		if (list1->val < list2->val)
		{
			tail->next = list1;
			tail = list1;
			list1 = list1->next;
		}
		else
		{
			tail->next = list2;
			tail = list2;
			list2 = list2->next;
		}
	}
	if (list1)
	{
		tail->next = list1;
	}
	if (list2)
	{
		tail->next = list2;
	}
	struct ListNode* list = head->next;
	free(head);
	return list;
	//return head->next;//存在内存泄露
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值