[Data structure and algorithm] Recognize the linked list in the linear list and realize the one-way linked list

This article follows the above. Above we have known the concept of linear tables and realized static and dynamic sequence tables. Next we recognize a new concept linked list. And realize various operations of one-way linked list. If you still don’t understand the sequence table, read this article

(13 messages) [Data structure and algorithm] Realize static and dynamic sequence tables in linear tables_Xiaowang Xuecode Blog-CSDN Blog

Table of contents

1. What is a linked list?

1. The concept and structure of linked list

2. Classification of linked lists

1. One-way or two-way

2. To lead or not to lead

3. Cyclic or non-cyclic

Second, the implementation of the linked list

1. Headless one-way acyclic linked list

2. Realization of functions

1. Initialize and print the linked list

2. Head plug and tail plug

3. Head delete and tail delete

4. Singly linked list search

5. Insert data before or after the pos node position

6. Delete data at pos node position (delete pos node)

7. Delete a node after the pos position

8. Destroy the linked list

3. Complete code

1.LinkList.h

2.LinkList.c

3.test.c


foreword

We know the concept of linear table in data structure, we should feel better to understand, because the establishment of sequential table mainly involves structure and dynamic memory management function, which is a form similar to array.

we have to think about such a question

1. To increase capacity, you need to apply for new space, copy data, and release old space, which will consume a lot of money.

2. Capacity expansion is generally 2 times expansion, and sometimes a certain amount of space will be wasted

Therefore, in order to solve the above problems, we introduced the concept of a linked list in a linear list.


1. What is a linked list?

1. The concept and structure of linked list

Concept: Linked list is a non-sequential and non-sequential storage structure in physical storage structure. The logical order of data elements is realized through the link order of pointers in the linked list.

The diagram is as follows:

As can be seen from the above figure, the characteristics of the linked list are:

1. The chain structure is logically continuous, but not necessarily physically continuous. (The address of each node is not necessarily)

2. Nodes in reality are generally applied from the heap.

3. The space applied from the heap is allocated according to a certain strategy, which varies according to different editors. The space applied again may be continuous or discontinuous.

2. Classification of linked lists

In fact, there are many structures of linked lists, and the following combinations have 8 main linked list structures.

1. One-way or two-way

2. To lead or not to lead

If the head node is used, there is no need to assign a value to its data field, and it only serves as a base point for establishing a linked list. If it is not used, the first node can store the value, and a new node is created for the first node. Just click phead, so the head node linked list becomes a non-head node linked list     

The main thing is to see if the first node uses its value field

Use the first node data field non-head node

useless       head node                        

3. Cyclic or non-cyclic

 

For the above types of situations, there are two commonly used ones, headless one-way non-circular linked list, and headed two-way circular linked list

as the picture shows

1. Headless one-way non-circular linked list: the structure is simple, and it is generally not used to store data alone. In practice, it is more of a substructure of other data structures, such as hash buckets, adjacency lists of graphs, and so on. In addition, this structure appears a lot in written test interviews.
2. Leading two-way circular linked list: the most complex structure, generally used to store data separately. The linked list data structure used in practice is a two-way circular linked list with the lead. In addition, although the structure is complex, after using the code to implement it, you will find that the structure will bring many advantages, and the implementation is simple. We will know when we implement the code later.

Second, the implementation of the linked list

1. Headless one-way acyclic linked list

The structure is:

typedef int SLDataType;
//单向链表的实现、
typedef struct ListNode {
	SLDataType data;//数据域
	struct ListNode* next;//指针域
}List;

The function to be implemented is:

//打印单链表
void ListPrint(List* ps);
//单链表的尾插
void ListPushBack(List** ps, SLDataType data);
//单链表的头插
void ListPushFront(List** ps, SLDataType data);
//单链表的尾删
void ListPopBack(List** ps);
//单链表的头删
void ListPopFront(List** ps);
//单链表的查找
List* ListFind(List* ps);

//在pos位置上插入数据
void ListInsertBefore(List** ps, SLDataType x, List* pos);

//在pos位置之后插入数据
void ListInsertAfter(List** ps, SLDataType x, List* pos);

//在pos位子删除数据
void ListErase(List** ps, List* pos);

//在pos位置之后一位删除数据
void ListEraseAfter(List* pos);
//单链表的摧毁
void ListDestory(List** ps);

2. Realization of functions

1. Initialize and print the linked list

//初始化链表
void InitList(List* ps) {
	ps->data = 0;
	ps->next = NULL;
}
//打印单链表
void ListPrint(List* ps) {
	List* cur = ps;
	while ((cur) != NULL) {
		printf("%d -> ", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}

2. Head plug and tail plug

The tail insertion diagram is as follows:

code show as below:

//创建一个新节点
List* CreateNode(SLDataType x) {
	List* newNode = (List*)malloc(sizeof(List));
	if (newNode == NULL) {
		perror("malloc fail\n");
		exit(-1);
	}
	else {
		newNode->data = x;
		newNode->next = NULL;
	}
	return newNode;
}
//单链表的尾插
void ListPushBack(List** ps, SLDataType data) {
	//创建新的节点
	assert(ps);//断言
	List* newNode = CreateNode(data);
	if (*ps == NULL) {
		//说明是空链表
		*ps = newNode;
	}
	else {
		List* tail = *ps;
		while (tail->next != NULL) {
			tail = tail->next;
		}
		tail->next = newNode;
	}
}

Insert the header as shown:

code show as below:

//单链表的头插
void ListPushFront(List** ps, SLDataType data) {
	//先断言是否为空
	assert(ps);
	//将新地址指向头结点下一个next结点的地址,然后在用头结点指向新节点
	List* newNode = CreateNode(data);
	newNode->next = (*ps);  //new指向ps当前的位置,然后new是第一个位置了,将new赋值给ps,这样new就作为头部连接链表了
	(*ps) = newNode;//原本ps位置的数值不变,这样的话就成 new->next=ps,new数值在前,ps的数值在后

}

3. Head delete and tail delete

Tail deletion is shown in the figure:

 Code demo:

//单链表的尾删
void ListPopBack(List** ps) {
	assert(ps);//断言
	//三种情况
	//1.空链表
	//2.一个节点
	//3.多个节点
	if (*ps == NULL) {
		return;
	}
	//只有一个节点的情况为
	else if ((*ps)->next == NULL) {
		free(*ps); //如果只有一个头节点的话
		*ps = NULL;
	}
	else {
		//多个节点的情况下、
		List* tail = *ps;
		while (tail->next->next!= NULL) {
			tail = tail->next;
		}
		free(tail->next);
		tail->next= NULL;
	}
}

The header is deleted as shown in the figure:

 code show as below:

//单链表的头删
void ListPopFront(List** ps) {
	assert(ps);
	//1.空
	//2.非空
	if (*ps == NULL) {
		//为空
		return;
	}
	else {
		List* tail = (*ps)->next;//创建临时变量tail,将头节点之后的地址给tail
		free(*ps);//滞空头节点
		*ps = NULL;//可有可不有,接下来也要用
		*ps = tail;//将tail也就是ps的下一个List节点给ps

	}
}

4. Singly linked list search

code show as below:

//单链表的查找

List* ListFind(List* ps,SLDataType data) {
	//进行查找就是进行判断是否为空链表,为空直接返回
	if (ps == NULL) {
		printf("链表为空、无法查找\n");
		return;
	}
	List* tail = ps;
	while (tail != NULL) {//从头节点开始,进行循环,
		if (tail->data == data) {
			return tail;
		}
		tail = tail->next;
	}
	return tail;//最后还找不到data,tail就为NULL了
}

5. Insert data before or after the pos node position

Insert data before the pos node position, as shown in the figure:

code show as below:

//在pos位置上插入数据
void ListInsertBefore(List** ps, SLDataType x, List* pos) {
	//先判断是否为空
	assert(ps);
	assert(pos);
	//空链表排除
	//1.pos是第一个节点
	//2.pos不是第一个节点
	if (*ps == pos) {
		//是第一个节点,那就直接头插
		ListPushFront(ps, x);
	}
	else {
		List* prev = *ps;
		while (prev->next != pos) {
			prev = prev->next;
		}
		List* newnode = CreateNode(x);
		prev->next = newnode;
		newnode->next = pos;
	}
}

Insert a node after the position of the pos node, as shown in the figure:

 code show as below:
 

//在pos位置之后插入数据
void ListInsertAfter(List** ps, SLDataType x, List* pos) {
	assert(ps);
	//assert(pos);//断言
	List* newnode = CreateNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

6. Delete data at pos node position (delete pos node)

as the picture shows:

code show as below:

//在pos位子删除数据
void ListErase(List** ps, List* pos) {
	assert(ps);
	assert(pos);

	if (*ps == pos) {
		ListPopFront(ps);
	}
	else {
		List* next = *ps;
		while (next->next != pos) {
			next = next->next;
		}
		//这个时候next->next == pos
		next->next = next->next->next;
		/*free(next->next);*/
		free(pos);
		pos = NULL;
	}
}

7. Delete a node after the pos position

as the picture shows:

code show as below:

//在pos位置之后一位删除数据
void ListEraseAfter(List* pos) {
	assert(pos);
	List* next = pos->next;//将pos 的下一个结点赋值给next
	if (next != NULL) {
		pos->next = pos->next->next;//表示pos的下一个的下一个结点的地址赋值给pos的指针域  实质上是将pos的下一个结点给跳过
		free(next);  //将pos的下一个结点给free释放
		next = NULL;  //next指向为NULL  防止野指针
	}
}

8. Destroy the linked list

code show as below:

//链表的摧毁  直接将头指针指针域指向NULL
void ListDestory(List** ps) {
	//assert(ps);  //防止空链表
	一个结点一个结点释放
	//List* next = *ps;
	//while (next) {
	//	List* cur = next->next;
	//	free(next);
	//	next = cur;
	//}
	*ps = NULL;
}

Because it is a secondary pointer, you can directly *ps=NULL, or free one by one

3. Complete code

1.LinkList.h

#define _CRT_SECURE_NO_WARNINGS

#include<stdio.h>
#include<stdlib.h>
#include<malloc.h>
#include<assert.h>
typedef int SLDataType;
//单向链表的实现、
typedef struct ListNode {
	SLDataType data;//数据域
	struct ListNode* next;//指针域
}List;

//打印单链表
void ListPrint(List* ps);
//单链表的尾插
void ListPushBack(List** ps, SLDataType data);
//单链表的头插
void ListPushFront(List** ps, SLDataType data);
//单链表的尾删
void ListPopBack(List** ps);
//单链表的头删
void ListPopFront(List** ps);
//单链表的查找
List* ListFind(List* ps);

//在pos位置上插入数据
void ListInsertBefore(List** ps, SLDataType x, List* pos);

//在pos位置之后插入数据
void ListInsertAfter(List** ps, SLDataType x, List* pos);

//在pos位子删除数据
void ListErase(List** ps, List* pos);

//在pos位置之后一位删除数据
void ListEraseAfter(List* pos);
//单链表的摧毁
void ListDestory(List** ps);

2.LinkList.c

#define _CRT_SECURE_NO_WARNINGS
#include"单向链表.h"

//链表的使用,在插入上面
// 如果是尾部插入,如果是空链表直接将新节点给ps 是先找到链表尾部,然后创建新节点,连接即可
// 如果是头部插入,先进行断言判空,之后创建新节点,将新节点的数据
// new->next=ps  这个是找到对应的位置,连接起来
// ps=new;  将新节点的信息传递给ps,这样ps还是头节点
// 
// 
// 
// 
// 


//进行单链表的实现

//初始化链表
void InitList(List* ps) {
	ps->data = 0;
	ps->next = NULL;
}
//打印单链表
void ListPrint(List* ps) {
	List* cur = ps;
	while ((cur) != NULL) {
		printf("%d -> ", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}

//创建一个新节点
List* CreateNode(SLDataType x) {
	List* newNode = (List*)malloc(sizeof(List));
	if (newNode == NULL) {
		perror("malloc fail\n");
		exit(-1);
	}
	else {
		newNode->data = x;
		newNode->next = NULL;
	}
	return newNode;
}
//单链表的尾插
void ListPushBack(List** ps, SLDataType data) {
	//创建新的节点
	assert(ps);//断言
	List* newNode = CreateNode(data);
	if (*ps == NULL) {
		//说明是空链表
		*ps = newNode;
	}
	else {
		List* tail = *ps;
		while (tail->next != NULL) {
			tail = tail->next;
		}
		tail->next = newNode;
	}
}
//单链表的头插
void ListPushFront(List** ps, SLDataType data) {
	//先断言是否为空
	assert(ps);
	//将新地址指向头结点下一个next结点的地址,然后在用头结点指向新节点
	List* newNode = CreateNode(data);
	newNode->next = (*ps);  //new指向ps当前的位置,然后new是第一个位置了,将new赋值给ps,这样new就作为头部连接链表了
	(*ps) = newNode;//原本ps位置的数值不变,这样的话就成 new->next=ps,new数值在前,ps的数值在后

}

//单链表的尾删
void ListPopBack(List** ps) {
	assert(ps);//断言
	//三种情况
	//1.空链表
	//2.一个节点
	//3.多个节点
	if (*ps == NULL) {
		return;
	}
	//只有一个节点的情况为
	else if ((*ps)->next == NULL) {
		free(*ps); //如果只有一个头节点的话
		*ps = NULL;
	}
	else {
		//多个节点的情况下、
		List* tail = *ps;
		while (tail->next->next!= NULL) {
			tail = tail->next;
		}
		free(tail->next);
		tail->next= NULL;
	}
}


//单链表的头删
void ListPopFront(List** ps) {
	assert(ps);
	//1.空
	//2.非空
	if (*ps == NULL) {
		//为空
		return;
	}
	else {
		List* tail = (*ps)->next;//创建临时变量tail,将头节点之后的地址给tail
		free(*ps);//滞空头节点
		*ps = NULL;//可有可不有,接下来也要用
		*ps = tail;//将tail也就是ps的下一个List节点给ps

	}
}

//单链表的查找

List* ListFind(List* ps,SLDataType data) {
	//进行查找就是进行判断是否为空链表,为空直接返回
	if (ps == NULL) {
		printf("链表为空、无法查找\n");
		return;
	}
	List* tail = ps;
	while (tail != NULL) {//从头节点开始,进行循环,
		if (tail->data == data) {
			return tail;
		}
		tail = tail->next;
	}
	return tail;
}

//在pos位置上插入数据
void ListInsertBefore(List** ps, SLDataType x, List* pos) {
	//先判断是否为空
	assert(ps);
	assert(pos);
	//空链表排除
	//1.pos是第一个节点
	//2.pos不是第一个节点
	if (*ps == pos) {
		//是第一个节点,那就直接头插
		ListPushFront(ps, x);
	}
	else {
		List* prev = *ps;
		while (prev->next != pos) {
			prev = prev->next;
		}
		List* newnode = CreateNode(x);
		prev->next = newnode;
		newnode->next = pos;
	}
}
//在pos位置之后插入数据
void ListInsertAfter(List** ps, SLDataType x, List* pos) {
	assert(ps);
	//assert(pos);//断言
	List* newnode = CreateNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}
//在pos位子删除数据
void ListErase(List** ps, List* pos) {
	assert(ps);
	assert(pos);

	if (*ps == pos) {
		ListPopFront(ps);
	}
	else {
		List* next = *ps;
		while (next->next != pos) {
			next = next->next;
		}
		//这个时候next->next == pos
		next->next = next->next->next;
		/*free(next->next);*/
		free(pos);
		pos = NULL;
	}
}
//在pos位置之后一位删除数据
void ListEraseAfter(List* pos) {
	assert(pos);
	List* next = pos->next;//将pos 的下一个结点赋值给next
	if (next != NULL) {
		pos->next = pos->next->next;//表示pos的下一个的下一个结点的地址赋值给pos的指针域  实质上是将pos的下一个结点给跳过
		free(next);  //将pos的下一个结点给free释放
		next = NULL;  //next指向为NULL  防止野指针
	}
}
//链表的摧毁  直接将头指针指针域指向NULL
void ListDestory(List** ps) {
	//assert(ps);  //防止空链表
	一个结点一个结点释放
	//List* next = *ps;
	//while (next) {
	//	List* cur = next->next;
	//	free(next);
	//	next = cur;
	//}
	*ps = NULL;
}

3.test.c

#define _CRT_SECURE_NO_WARNINGS

#include"单向链表.h"
void test()
{
	List* phead=NULL;//作为头节点

	//单链表的尾插
	ListPushBack(&phead, 1);
	ListPushBack(&phead, 2);
	ListPushBack(&phead, 3);
	ListPushBack(&phead, 4);
	ListPushBack(&phead, 5);
	ListPrint(phead);

	ListPushFront(&phead, 1);
	ListPrint(phead);

	ListPopBack(&phead);
	ListPrint(phead);
	
	ListPopFront(&phead);
	ListPrint(phead);
	ListErase(&phead, phead->next);
	ListInsertAfter(&phead, 10, phead->next);
	ListEraseAfter(phead->next);

	ListPrint(phead);
	ListDestory(&phead);
}
int main()
{
	test();
	return 0;
}

Summarize

This article mainly explains what are the classifications of linked lists. There are two commonly used types, headless one-way non-circular linked list and headed two-way circular linked list. We have implemented headless one-way non-circular linked list, which is a relatively simple linked list. For implementation, we use a second-level pointer to pass parameters. Of course, it is also possible to use a first-level pointer to pass parameters. The main implementation functions are head-to-tail insertion, head-to-tail deletion, and adding or deleting elements before and after pos specifies the node position.

Next, we will introduce to you another form of the most commonly used linked list below, taking the lead in bidirectional circular linked list.

Guess you like

Origin blog.csdn.net/qq_63319459/article/details/128766484