数据结构之链表

数据结构之链表
链表以线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针(Pointer)。由于不必须按顺序存储,链表的插入和删除操作可以达到O(1)的复杂度。而链表有指向性可以分为单向链表和双向链表。

单向链表(单链表)是链表的一种,它由节点组成,每个节点都包含下一个节点的指针,下图就是一个单链表。

在这里插入图片描述
可以由图看出,链表中每个节点都指向下个节点。一般在设置链表时,头节点我一般会设成空节点。
在编写单向链表时,我一般会这样设置链表节点的结构体。

// 定义链表结构体
typedef struct ListNode
{
	int data;				//数据域
	struct ListNode* next;	//下个节点的地址
}List;

数据域可以是自定义类型的数据,这里只是举个例子。其中还有一个指针变量用于指向下一个节点。通常链表中最后一个节点的指针变量指向的是空地址。
单向链表的基本操作有:
初始化链表,用于创建一个表头方便以后的数据的存储。代码我是运行过的,但是也可能会有bug,如果有发现还望告知。

// 初始化链表
List* InitList()
{
	// 申请链表头空间
	List* pHead = (List*)malloc(sizeof(struct ListNode));
	if (pHead == NULL) return NULL;	//如果申请空间失败返回空
	//初始化头节点
	pHead->data = -1;
	pHead->next = NULL;
	ListSize = 0;

	printf("请输入节点数据,以-1结束:\n");
	int val;
	List* pCurrent = pHead;
	while (1)
	{
		scanf("%d",&val);
		if (val == -1) break;
		//创建新节点
		List* newNode = (List*)malloc(sizeof(struct ListNode));
		newNode->data = val;
		newNode->next = NULL;
		ListSize += 1;
		pCurrent->next = newNode;
		pCurrent = newNode;
	}
	return pHead;
}

一般为了知道链表中有那些数据我们需要遍历链表。

// 遍历链表
void foreach_list(List* pHead)
{
	if (!pHead) return;
	List* pCurrent = pHead->next;
	while (pCurrent)
	{
		printf("%d\n", pCurrent->data);
		pCurrent = pCurrent->next;
	}
}

关于链表节点的插入我写了四种插入方式,其实具体操作都大同小异。

// 链表尾插
void insert_tail(List* pHead, int val)
{
	if (!pHead)	return;
	List* pCurrent = pHead;
	while (pCurrent->next)
	{
		pCurrent = pCurrent->next;
	}
	List* newNode = (List*)malloc(sizeof(struct ListNode));
	newNode->data = val;
	newNode->next = NULL;
	pCurrent->next = newNode;
}

// 链表头插
void insert_head(List* pHead, int val)
{
	if (!pHead) return;
	List* newNode = (List*)malloc(sizeof(struct ListNode));
	newNode->data = val;
	newNode->next = pHead->next;
	pHead->next = newNode;
}

// 根据下标插入节点
void insert_index(List* pHead, int index, int val)
{
	if (!pHead) return;
	int i = 0;
	List* pCurrent = pHead->next;
	while (i < index)
	{
		pCurrent = pCurrent->next;
		if (!pCurrent)
		{
			printf("超出链表范围!\n");
			return;
		}
		i++;
	}
	List* newNode = (List*)malloc(sizeof(struct ListNode));
	newNode->data = val;
	if (!pCurrent->next)
	{
		newNode->next = NULL;
	}
	newNode->next = pCurrent->next;
	pCurrent->next = newNode;
}

// 根据指定元素位置插入节点(插在指定位置的后面)
void insert(List* pHead, int oldval, int newval)	// oldval代表指定元素,newval表示插入节点的值
{
	if (!pHead) return;
	List* pCurrent = pHead->next;
	while (pCurrent->data != oldval)
	{
		pCurrent = pCurrent->next;
		if (pCurrent == NULL)
		{
			printf("找不到指定元素!\n");
			return;
		}
	}
	List* newNode = (List*)malloc(sizeof(struct ListNode));
	newNode->data = newval;
	if (!pCurrent->next) 
	{
		newNode->next = NULL;
	}
	newNode->next = pCurrent->next;
	pCurrent->next = newNode;
	
}

删除节点,一般链表不会大规模的删除节点,因为每次删除节点都要遍历链表,会大大的浪费内存资源。

// 删除尾部节点
void del_tail(List* pHead)
{
	if (!pHead || !pHead->next) return;
	
	List* pCurrent = pHead->next;
	List* pNext = pCurrent->next;

	while (pNext->next)
	{
		pCurrent = pNext;
		pNext = pNext->next;
	}
	free(pNext);
	pNext = NULL;
	pCurrent->next = NULL;
}

// 删除头部节点
void del_head(List* pHead)
{
	if (!pHead || !pHead->next) return;
	List* pCurrent = pHead->next;
	pHead->next = pCurrent->next;
	free(pCurrent);

}

// 删除指定位置节点
void del_node(List* pHead, int val)
{
	if (!pHead || !pHead->next || val == -1) return;
	List* pre = pHead;
	List* pCurrent = pHead->next;
	
	while (pCurrent->data != val)
	{
		pre = pCurrent;
		pCurrent = pCurrent->next;
		if (pCurrent == NULL)
		{
			printf("找不到指定元素!\n");
			return;
		}
	}

	pre->next = pCurrent->next;
	free(pCurrent);
}

链表的清空,销毁操作。

// 清空链表
void clear_list(List* pHead)
{
	if (!pHead || !pHead->next)	return;
	List* pCurrent = pHead->next;
	List* Node = pCurrent;
	while (!pCurrent)
	{
		pCurrent = pCurrent->next;
		free(Node);
		Node = pCurrent;
	}
	pHead->next = NULL;
}

// 销毁链表
void destory_list(List* pHead)
{
	clear_list(pHead);
	free(pHead);
	pHead = NULL;
}

有时候我们需要翻转链表。

// 反转链表
void reverse_list(List* pHead)
{
	if (!pHead || !pHead->next) return;
	
	List* pCurrent = pHead->next;
	if (!pCurrent->next) return;
	List* Next = pCurrent->next;
	List* Node = NULL;

	pCurrent->next = NULL;
	while (Next)
	{
		Node = Next->next;
		Next->next = pCurrent;
		pCurrent = Next;
		Next = Node;
	}
	pHead->next = pCurrent;
}

在获取链表大小,一般有两种方法,一种就是设定一个变量记录链表中节点的数量,另一种是遍历链表,获取链表大小。前者浪费内存,后者浪费时间。

// 返回链表大小
int listsize(List* pHead)
{
	if (!pHead || !pHead->next) return 0;
	int count = 0;
	List* pCurrent = pHead->next;
	while (pCurrent)
	{
		count++;
		pCurrent = pCurrent->next;
	}
	return count;
}


既然单向链表在存储数据的时候这么麻烦,为什么还要用单向链表也不用数组等便于访问的数据结构呢?
一、链表的插入和删除一个节点的操作是O(1)的复杂度,而数组的除了尾插或尾删其他操作的复杂度都要大于链表。
二、在存储空间方面,链表的存储空间可以是不连续的,因为链表可以通过地址的指向性链接所有节点,并且可以实现动态内存申请。而数组方面,则需要先申请一段连续的空间保存数据,如果空间不足需要再申请更大的空间,将原有的数据拷贝到新空间的地址上,再释放旧空间。若空间申请过大会造成资源浪费。
三、留给你们说?

同时链表的缺点也很明显:
一、访问某一节点要遍历链表,不像数组通过下标访问数据。
二、翻转链表时不仅要遍历链表还有多个变量辅助。

通过,改进链表结构体可以提高链表翻转的效率,就是使用双向链表。
先看看双向链表的实现代码

typedef struct Double_List	// 双向链表结构体
{
	struct Double_List* pre;	// 前驱节点的地址
	int data;					// 数据域
	struct Double_List* next;	// 后继节点的地址
}d_list;

d_list* Init_dlist()				// 双向链表
{
	// 为头节点申请空间
	d_list* head = (d_list*)malloc(sizeof(struct Double_List));
	// 初始化头节点
	head->pre = NULL;
	head->data = -1;
	head->next = NULL;

	d_list* pCurrent = head;
	int val;
	printf("输入节点数据,以-1结束:\n");
	d_list* newNode = NULL;
	while (1)
	{
		scanf("%d", &val);

		if(val == -1)
		{ 
			break;
		}

		newNode = (d_list*)malloc(sizeof(struct Double_List));
		newNode->data = val;
		newNode->pre = pCurrent;
		newNode->next = NULL;
		pCurrent->next = newNode;
		pCurrent = newNode;
	}
	return head;
}
void foreach_dlist(d_list* head)	// 遍历双向链表
{
	if (!head) return;
	d_list* pCurrent = head->next;
	while (pCurrent)
	{
		printf("%d\n", pCurrent->data);
		pCurrent = pCurrent->next;
	}
}

void insert(d_list* head, int data)	//头插
{
	if (!head) return;
	d_list* newNode = (d_list*)malloc(sizeof(struct Double_List));
	newNode->pre = head;
	newNode->next = head->next;
	newNode->data = data;
	if (head->next)
	{
		head->next->pre = newNode;
	}
	head->next = newNode;
}

void insert_tail(d_list* head, int data)	//尾插
{
	if (!head) return;
	d_list* pCurrent = head;
	while (pCurrent->next)
	{
		pCurrent = pCurrent->next;
	}
	d_list* newNode = (d_list*)malloc(sizeof(struct Double_List));
	newNode->data = data;
	newNode->pre = pCurrent;
	newNode->next = NULL;
	pCurrent->next = newNode;
}

void insertbydata(d_list* head, int oldval, int newval)	//根据现有数据的值插入新节点
{
	if (!head) return;

	d_list* pCurrent = head->next;
	while (pCurrent)
	{
		if (pCurrent->data == oldval)
		{
			break;
		}
		pCurrent = pCurrent->next;
	}
	if (!pCurrent)
	{
		printf("找不到关键字!\n");
		return;
	}
	d_list* newNode = (d_list*)malloc(sizeof(struct Double_List));
	newNode->data = newval;
	newNode->pre = pCurrent;
	newNode->next = pCurrent->next;
	pCurrent->next = newNode;

}

void insertbyindex(d_list* head, int index, int data)		//根据下标插入数据
{
	if (!head) return;
	if (index == 0)
	{
		insert(head, data);
		return;
	}
	d_list* pCurrent = head->next;
	int i = 0;
	while (i < index) 
	{
		pCurrent = pCurrent->next;
		i++;
		if (!pCurrent)
		{
			printf("超出链表范围!\n");
			return;
		}
	}
	if (!pCurrent->next)
	{
		insert_tail(head, data);
		return;
	}

	d_list* newNode = (d_list*)malloc(sizeof(struct Double_List));
	newNode->data = data;
	newNode->pre = pCurrent->pre;
	newNode->next = pCurrent;
	pCurrent->pre->next = newNode;
	pCurrent->pre = newNode;
	
}

void del_node(d_list* head, int data)	 // 删除某节点
{
	if (!head) return;
	d_list* pCurrent = head->next;
	while (pCurrent)
	{
		if (pCurrent->data == data)
		{
			break;
		}
		pCurrent = pCurrent->next;
	}
	pCurrent->pre->next = pCurrent->next;
	free(pCurrent);
	pCurrent = NULL;
}

void find_node(d_list* head, int data)	 // 寻找某个节点
{
	if (!head) return;
	d_list* pCurrent = head->next;
	
	while (pCurrent)
	{
		if (pCurrent->data == data)
		{
			printf("找到了,%d\n", pCurrent->data);
			return;
		}
		pCurrent = pCurrent->next;
	}
	printf("没找到!\n");
}

void alter_node(d_list* head, int olddata, int newdata) // 修改某个节点
{
	if (!head) return;
	d_list* pCurrent = head->next;
	while (pCurrent)
	{
		if (pCurrent->data == olddata)
		{
			pCurrent->data = newdata;
			return;
		}
		pCurrent = pCurrent->next;
	}
	printf("没有找到想要替换的值!\n");

}

void clear_list(d_list* head)	// 清空链表
{
	if (!head) return;
	d_list* pCurrent = head->next;
	d_list* next = NULL;

	while (pCurrent)
	{
		next = pCurrent->next;
		free(pCurrent);
		pCurrent = next;
	}

	head->next = NULL;
}

void destory_list(d_list* head)	// 销毁链表
{
	clear_list(head);
	if (head)
	{
		free(head);
		head = NULL;
	}
}

void reverse_list(d_list* head)	// 翻转链表
{
	if (!head) return;

	d_list* pCurrent = head->next;
	
	head->next = NULL;

	d_list* next = NULL;
	
	while (pCurrent)
	{
		next = pCurrent->next;
		pCurrent->next = head->next;
		pCurrent->pre = next;
		head->next = pCurrent;
		pCurrent = next;
	}
}

int list_size(d_list* head)	// 返回链表大小
{
	if (!head)return;
	d_list* pCurrent = head->next;
	
	int count = 0;
	while (pCurrent)
	{
		count++;
		pCurrent = pCurrent->next;
	}
	return count;
}


接下来介绍一下单向链表和双向链表的区别
单向链表:

由两部分组成:数据域和指针域,每个结点都有一个指针,每个节点指针的指向都是指向自身结点的下一个结点,最后一个结点的head指向为null,对单链表的操作只能从一端开始,如果需要查找链表中的某一个结点,则需要从头开始进行遍历。

双向链表:

对于双向链表来说,它的每个节点要指向“直接前驱”和“直接后继”,所以节点类需要含有两个指针域。指向直接前驱的指针使用pre表示,指向后继的指针使用next表示。双向链表是在单向链表基础上的一个改进,每个节点指向其直接前驱和直接后继节点。因此,从双向链表的任意位置开始,都能访问所有的节点。
双向链表从节点的结构上可以看出,双向链表的所需的存储空间大于单向链表。同时,对于插入和删除等操作来说,双向链表的节点操作更加复杂,涉及到节点的前后两个节点。

数据结构之链表_第1张图片

你可能感兴趣的:(学习笔记,链表,数据结构,list)