我的学习记录-数据结构(C语言)-单链表

2022-9-11
看了单链表,尝试写了一点,有了一点心得;
英语还是得学,不然命名还得靠翻译;
不清楚怎么算标准的命名,只好直接填单词了;
不是科班的,自学难免犯错,如果发现错误请直接纠正,非常感谢;
初始化和插入等操作需要用二级指针;

#define error -1
typedef int ElemType 

定义单链表

typedef struct LNode {										//定义单链表 L就是头结点
	ElemType data;
	struct LNode* next;
}LNode, * LinkList;
//上面等同于下面
typedef struct LNode LNode;							
typedef LNode *LinkList;

这里以前不是很清楚,为什么这么写,只记了个形式;
LNode强调是一个结点,LinkList强调其是一个链表,
LinkList L 和 LNode *L 是同一个东西,L就是一个指向链表开头的指针,找到L就能找到后续的结点;
结点L(代表整个链表)->第一个元素->第二个元素…第n个元素->NULL
L就是其头结点,可以添加指针Head标识

初始化

void InitList(LinkList* L) {								//初始化,使用二级指针
	*L = (LinkList)malloc(sizeof(LNode));
		(*L)->next = NULL;
	if ((*L) == NULL) {
		printf("Initialize Failed\n");
	}
	else {
		printf("success\n");
	}
}

头插法建立链表

void Create_List_HeadInsert(LinkList* L) {				//已有链表情况下,头插法插入数据(逆序)
	int x;
	scanf_s("%d", &x);
	LNode* newnode;
	while (x != 9999) {
		LNode* newnode = (LNode*)malloc(sizeof(LNode));
		newnode->data = x;
		newnode->next = (*L)->next;
		(*L)->next = newnode;
		scanf_s("%d", &x);
	}
}

这种方法建立的链表与输入顺序是相反的,输入1,2,3,4,建立的链表是4,3,2,1;

判断是否为空

思路是判断L后面的结点是否存在
L是否存在
L->next是否存在

int Empty_list(LinkList L) {								//判空
	if (L == NULL) {
		return error;
	}
	else {
		if (L->next == NULL) {
			return 1;
		}
		else {
			return 0;
		}
	}
}

好像可以使用bool类型的函数,不过要加#include <stdbool.h>

表长

思路是遍历整个链表,每经过一个结点就使计数器+1
L结点不计入在内
前提是L存在

int ListLength(LinkList L) {															//表长
	int count = 0;
	if (L != NULL) {
		LNode* p = L->next;
		while (p != NULL) {
			count++;
			p = p->next;
		}
		return count;
	}
	else {
		printf("LinkList Not Found! error=");
		return error;
	}
}

遍历

思路是新建一个指针tl_tmp指向第一个元素,当该结点后还有结点是打印其data域的值,即tl_tmp != NULL

void Traverse_List(LinkList L) {						//遍历含尾节点的全部节点的data域
	if (L == NULL) {
		printf("ERROR\n");
	}
	else {
		if (L->next != NULL) {
			LNode* tl_tmp = L->next;
			while (tl_tmp != NULL) {
				printf("%p,%d,%p\n", tl_tmp, tl_tmp->data, tl_tmp->next);	//按 结点地址, data域值, next域地址 顺序打印
				tl_tmp = tl_tmp->next;
			}

			printf("Traverse Completed\n");

		}
		else {																//L->next == NULL则为空表
			printf("EMPTY LIST\n");
		}
	}
}

向链表中插入元素

void ListInsert(LinkList* L, int i, ElemType e) {        //在第i个位置插入e
	if ((*L) == NULL) {
		printf("ERROR!\n");
	}
	if (i <= ListLength(*L) + 1 && i >= 0) {
		LNode* p = (LNode*)malloc(sizeof(LNode));
		p->data = e;
		LNode* q = *L;
		for (int x = 1; x < i; x++) {
			q = q->next;
		}
		p->next = q->next;
		q->next = p;
	}
	else {
		printf("Invalid Input\n");
	}
}

malloc和free一般是成对出现的,但插入新结点时需要新分配一块内存,此时需要保留这块这块内存所以暂时不用free,待使用完链表后销毁链表,所有结点都会被释放

删除指定位置的结点并用e返回被删除结点的data域的值
思路是新建指针pre_p 和 p,用p指示第i个结点,pre_p 指示 p的前一个结点以便连接p前后两个结点

ElemType ListDelete(LinkList *L, int i) {				//删除第i个元素并返回被删除元素的值
	if ((*L) == NULL) {
		printf("LinkList Not Found! error=");
		return error;
	}
	else {
		if ((*L)->next == NULL) {
			printf("Empty LinkList! error=");
			return 0;
		}
		else
		{
			LNode* pre_p = *L;
			for (int x = 1; x < i; x++) {
				pre_p = pre_p->next;
			}
			LNode* p = pre_p->next;
			pre_p->next = p->next;
			p->next = NULL;
			int e = p->data;
			free(p);
			p = NULL;
			return e;
		}
	}
}

删除结点需要释放掉被删除结点的内存,所以在结束时应当free该结点,并使指针指空

删除指定结点

void NodeDelete(LinkList L, LNode *p) {				//删除指定结点(结点必须是L中的结点)
	if (L == NULL) {
		printf("LinkList Not Found!");
	}
	else {
		if (L->next == NULL) {
			printf("Empty LinkList! error=");
			return 0;
		}
		else
		{
			LNode* pre_p = *L;
			while (pre_p != NULL && pre_p->next != p) {
				pre_p = pre_p->next;
			}
			LNode* p = pre_p->next;
			if (p->next != NULL) {
				pre_p->next = p->next;
				p->next = NULL;
			}
			else {
				pre_p->next = NULL;
			}
			free(p);
			p = NULL;
		}
	}
}

思路是新建指针标识第一个元素,然后移动至第i个元素并用e修改其data域的值

void ListRevise(LinkList *L, int i, ElemType e) {//修改第i位结点的值
	if ((*L) == NULL || i <= 0 || i > ListLength(*L) || (*L)->next == NULL) {
		printf("Invalid Input");
	}
	else {
		LNode* p = *L;
		for (int x = 0; x < i; x++) {
			p = p->next;
		}
		p->data = e;
	}
}

按值查找
思路是遍历整个链表同时比较data域的值,在找到符合要求的第一个结点时返回其地址

LNode* LocateElem(LinkList L, ElemType e) {				//查找结点中第一个data域为e的结点并返回该结点指针(按值查找)
	if (L == NULL) {
		printf("LinkList Not Found!");
		return NULL;
	}
	else {
		if (L->next == NULL) {
			printf("Empty List!");
			return NULL;
		}
		else {
			LNode* tmp = L->next;
			while (tmp->data != e && tmp != NULL) {
				tmp = tmp->next;
			}
			if (tmp == NULL) {
				printf("Element Not Found!");
			}
			else {
				return tmp;
			}
		}
	}
}

按位查找
思路是新建一个指针标识第一个元素,然后移动至第i个元素并返回其data域的值

ElemType GetElem(LinkList L, int i) {					//按位查找表L中第i个元素的值并返回,空表返回0,无效值返回-1
	if (L == NULL) {
		printf("Invallid Input! error=");
		return error;

	}
	else {
		if (i <= ListLength(L) && i>0) {
			LNode* tmp = L->next;
			for (int x = 1; x < i; x++) {
				tmp = tmp->next;
			}
			return tmp->data;
		}
		else {
			printf("Invalid Input! error=");
			return 0;
		}
	}

按位返回结点

LNode* GetNode(LinkList L,int i) {//按位返回结点
	if (L == NULL) {
		printf("Invallid Input! error=");
		return error;

	}
	else {
		if (i <= ListLength(L) && i>0) {
			LNode* tmp = L->next;
			for (int x = 1; x < i; x++) {
				tmp = tmp->next;
			}
			return tmp;
		}
		else {
			printf("Invalid Input! error=");
			return 0;
		}
	}
}

按值返回位置
思路是遍历同时计数至第一个值为e的结点,返回计数器的值

int LocateElem_Sequence(LinkList L, ElemType e) {		//返回给定值的次序,返回值-1:不存在L;0:空表或未找到
	if (L == NULL) {
		printf("Invalid Input! error=");
		return error;
	}
	else {
		if (L->next == NULL) {
			return 0;
		}
		else {
			int count = 1;
			LNode* tmp = L->next;
			while (tmp->data != e) {
				if (tmp->next != NULL) {
					tmp = tmp->next;
					count++;
				}
				else {
					if (tmp->data == e) {
						return count;
					}
					else {
						return 0;
					}
				}
			}
		}
	}
}

销毁链表

思路是逐步销毁L->next结点直至全部为空

void ListDestroy(LinkList* L) {							//销毁链表并释放空间
	LNode* p = *L;
	while (*L) {
		p = (*L)->next;
		free(*L);
		(*L) = p;
	}
	if (!(*L)) {
		printf("Destroy Completed\n");
	}
}

复制链表

思路是逐个读取L中结点的data域的值,并插入L_copy 中

void LinkListCopy(LinkList L, LinkList* L_copy) {//复制链表,使用前先创建一个新链表L_copy接收复制的数据域
	if (L == NULL || L->next == NULL || (*L_copy) == NULL) {
		printf("Can Not Copy!");
	}
	else {
		LNode* p = L->next;
		int i = 1;
		while (p != NULL) {
			ListInsert((*L_copy), i, p->data);
			p = p->next;
			i++;
		}

	}
}

交换结点

(不改变原来的地址,仅改变次序)
思路是先用指针pre_a和 pre_b标识a,b两个结点的前一个结点的位置,再从链表中取出两个元素互换次序后再插入对应位置

void ListNode_Swap(LinkList *L, LNode* a, LNode* b) { //交换结点
	if (a == NULL || b == NULL) {
		printf("error!");
	}
	else {
		if (a != b) {
			LNode* pre_a = *L;
			while (pre_a->next != a) {
				pre_a = pre_a->next;
			}
			pre_a->next = a->next;
			a->next = NULL;
			LNode* pre_b = *L;
			while (pre_b->next != b) {
				pre_b = pre_b->next;
			}
			pre_b->next = b->next;
			b->next = NULL;
			a->next = pre_b->next;
			pre_b->next = a;
			b->next = pre_a->next;
			pre_a->next = b;
		}
	}
}

逆置链表

思路有三种:
一种是不断交换两端的元素,如再一个有5个元素的链表中,1和5换,2和4换,只要一直调用交换结点的函数即可,不过很慢,不推荐

LinkList LinkListInverted(LinkList L) {
	if (L == NULL || L->next == NULL) {
		printf("Inverted Failed");
	}
	else {
		int length = ListLength(L);
		for (int i = 0; i < length / 2; i++) {
			ListNode_Swap(L, GetNode(L, i+1),GetNode( L,length - i));
		}
		return L;
	}
}

一种是使用一个指针指示表尾,并获取其data域的值,然后用头插法将值插入一个新的空表内,头插法插入数据是倒着插入的,所以倒着读取再插入即可获取逆置的链表,也可以只使用原本的空间,比交换结点要快。

LinkList LinkListInverted_a(LinkList L,LinkList L_inverted) {//逆置链表,但不使用原先的链表,创建一个新链表接收逆置的数据域,逆置的链表返回为L_inverted
	if(L == NULL || L->next == NULL || L_inverted == NULL) {
		printf("Inverted Failed");
	}
	else {
		LNode* p = L->next;
		while (p != NULL) {
			LNode* q = (LNode*)malloc(sizeof(LNode));
			q->data = p->data;
			q->next = L_inverted->next;
			L_inverted->next = q;
			p = p->next;
		}
		return L_inverted;
	}	
}
void LinkListInverted(LinkList *L) { //逆置链表,使用原来的空间
    LNode* p = (*L)->next;
    LNode* q = p->next;
    (*L)->next = NULL;
    while (p != NULL) {
        p->next = (*L)->next;
        (*L)->next = p;
        p = q;
        if (q != NULL) {
            q = q->next;
        }
    }
}

学习心得

malloc与free一般成对出现,分配空间后仍需要的结点不用free,不需要的应该及时free,并使其指针指空;
读取到被free的指针会报错,一般是0xfffffffffffffff7;
读取到next域为NULL的结点后强行读取其next域,也会报错0xfffffffffffffff7;
LinkList是一个指针,LNode是一个结点;
内存空间就像是一片沙滩,指针可以帮助找到需要的那一粒沙子,所以需要标识的结点应提前用指针标识方便寻找;
命名时应注意大小写使易于阅读;
凡是涉及链表本身的操作都应使用二级指针;

这里是全部的代码

加上#include "LinkList.h"即可使用

#ifndef _LinkList_H_
#define _LinkList_H_

typedef int ElemType;										//定义data域元素类型
#define error -1
#define OK 1

typedef struct LNode {										//定义单链表 
	ElemType data;
	struct LNode* next;
}LNode, * LinkList;

void InitList(LinkList* L) {								//初始化,使用二级指针
	*L = (LinkList)malloc(sizeof(LNode));
		(*L)->next = NULL;
	if ((*L) == NULL) {
		printf("Initialize Failed\n");
	}
	else {
		printf("success\n");
	}
}

int Empty_list(LinkList L) {								//判空
	if (L == NULL) {
		return error;
	}
	else {
		if (L->next == NULL) {
			return 1;
		}
		else {
			return 0;
		}
	}
}


void Create_List_HeadInsert(LinkList* L) {				//已有链表情况下,头插法插入数据(逆序)
	int x;
	scanf_s("%d", &x);
	LNode* newnode;
	while (x != 9999) {
		LNode* newnode = (LNode*)malloc(sizeof(LNode));
		newnode->data = x;
		newnode->next = (*L)->next;
		(*L)->next = newnode;
		scanf_s("%d", &x);
	}
}

LinkList Create_List_TailInsert(LinkList L) {				//已有链表情况下,尾插法插入数据
	int x;
	scanf_s("%d", &x);
	LNode* t = L;
	while (x != 9999) {
		LNode* s = (LNode*)malloc(sizeof(LNode));
		s->data = x;
		s->next = t->next;
		t->next = s;
		t = t->next;
		scanf_s("%d", &x);
	}
	return L;
}

int ListLength(LinkList L) {//表长
	int count = 0;
	if (L != NULL) {
		LNode* p = L->next;
		while (p != NULL) {
			count++;
			p = p->next;
		}
		return count;
	}
	if (!L) {
		printf("LinkList Not Found! error=");
		return error;
	}
}

void Traverse_List(LinkList L) {						//遍历含尾节点的全部节点的data域
	if (L == NULL) {
		printf("ERROR\n");
	}
	else {
		if (L->next != NULL) {
			LNode* tl_tmp = L->next;
			while (tl_tmp != NULL) {
				printf("%p,%d,%p\n", tl_tmp, tl_tmp->data, tl_tmp->next);
				tl_tmp = tl_tmp->next;
			}
			printf("Traverse Completed\n");

		}
		else {
			printf("EMPTY LIST\n");
		}
	}
}

void Traverse_a(LinkList L) {//递归法遍历
	LNode* p = L;
	if (p == NULL) {
		return;
	}
	printf("%p,%d,%p\n", p, p->data, p->next);
	return Traverse_a(p->next);
}

void ListRevise(LinkList L, int i, ElemType e) {//修改第i位结点的值
	if (L == NULL || i <= 0 || i > ListLength(L) || L->next == NULL) {
		printf("Invalid Input");
	}
	else {
		LNode* p = L;
		for (int x = 0; x < i; x++) {
			p = p->next;
		}
		p->data = e;
	}
}

void ListInsert(LinkList* L, int i, ElemType e) {        //在第i个位置插入e
	if ((*L) == NULL) {
		printf("ERROR!\n");
	}
	if (i <= ListLength(*L) + 1 && i >= 0) {
		LNode* p = (LNode*)malloc(sizeof(LNode));
		p->data = e;
		LNode* q = *L;
		for (int x = 1; x < i; x++) {
			q = q->next;
		}
		p->next = q->next;
		q->next = p;
	}
	else {
		printf("Invalid Input\n");
	}
}

LNode* LocateElem(LinkList L, ElemType e) {				//查找结点中第一个data域为e的结点并返回该结点指针(按值查找)
	if (L == NULL) {
		printf("LinkList Not Found! error=");
		return NULL;
	}
	else {
		if (L->next == NULL) {
			printf("Empty List!");
			return NULL;
		}
		else {
			LNode* tmp = L->next;
			while (tmp->data != e && tmp != NULL) {
				tmp = tmp->next;
			}
			if (tmp == NULL) {
				printf("Element Not Found!");
			}
			else {
				return tmp;
			}
		}
	}
}

int LocateElem_Sequence(LinkList L, ElemType e) {		//返回给定值的次序,返回值-1:不存在L;0:空表或未找到
	if (L == NULL) {
		printf("Invalid Input! error=");
		return error;
	}
	else {
		if (L->next == NULL) {
			return 0;
		}
		else {
			int count = 1;
			LNode* tmp = L->next;
			while (tmp->data != e) {
				if (tmp->next != NULL) {
					tmp = tmp->next;
					count++;
				}
				else {
					if (tmp->data == e) {
						return count;
					}
					else {
						return 0;
					}
				}
			}
		}
	}
}

ElemType GetElem(LinkList L, int i,ElemType *e) {					//按位查找表L中第i个元素的值并返回,空表返回0,无效值返回-1,L不存在返回-2
	if (L == NULL) {
		printf("Invallid Input! error=");
		return -2;

	}
	if (L->next == NULL) {								//判断是否为空表
		printf("Invallid Input! error=");
		return 0;
	}
	else {
		if (i <= ListLength(L) && i > 0) {
			LNode* tmp = L->next;
			for (int x = 1; x < i; x++) {
				tmp = tmp->next;
			}
			*e = tmp->data;
			return OK;
		}
		else {
			printf("Invalid Input! error=");
			return -1;
		}
	}
}

LNode* GetNode(LinkList L, int i) {//按位返回结点
	if (L == NULL) {
		printf("Invallid Input! error=");
		return error;

	}
	else {
		if (i <= ListLength(L) && i > 0) {
			LNode* tmp = L->next;
			for (int x = 1; x < i; x++) {
				tmp = tmp->next;
			}
			return tmp;
		}
		else {
			printf("Invalid Input! error=");
			return 0;
		}
	}
}

ElemType ListDelete(LinkList L, int i) {				//删除第i个元素并返回被删除元素的值
	if (L == NULL) {
		printf("LinkList Not Found! error=");
		return error;
	}
	else {
		if (L->next == NULL) {
			printf("Empty LinkList! error=");
			return 0;
		}
		else
		{
			LNode* pre_p = *L;
			for (int x = 1; x < i; x++) {
				pre_p = pre_p->next;
			}
			LNode* p = pre_p->next;
			pre_p->next = p->next;
			p->next = NULL;
			int e = p->data;
			free(p);
			p = NULL;
			return e;
		}
	}
}

void NodeDelete(LinkList* L, LNode* p) {				//删除指定结点(结点必须是L中的结点)
	if ((*L) == NULL) {
		printf("LinkList Not Found!");
	}
	else {
		if ((*L)->next == NULL) {
			printf("Empty LinkList! error=");
			return 0;
		}
		else
		{
			LNode* pre_p = *L;
			while (pre_p != NULL && pre_p-> != p) {
				pre_p = pre_p->next;
			}
			LNode* p = pre_p->next;
			if (p->next != NULL) {
				pre_p->next = p->next;
				p->next = NULL;
			}
			else {
				pre_p->next = NULL;
			}
			free(p);
			p = NULL;
		}
	}
}

void ListDestroy(LinkList* L) {							//销毁链表并释放空间
	LNode* p = *L;
	while (*L) {
		p = (*L)->next;
		free(*L);
		(*L) = p;
	}
	if (!(*L)) {
		printf("Destroy Completed\n");
	}
}



void ListNode_Swap(LinkList L, LNode* a, LNode* b) { //交换结点
	if (a == NULL || b == NULL) {
		printf("error!");
	}
	else {
		if (a != b) {
			LNode* pre_a = L;
			while (pre_a->next != a) {
				pre_a = pre_a->next;
			}
			pre_a->next = a->next;
			a->next = NULL;
			LNode* pre_b = L;
			while (pre_b->next != b) {
				pre_b = pre_b->next;
			}
			pre_b->next = b->next;
			b->next = NULL;
			a->next = pre_b->next;
			pre_b->next = a;
			b->next = pre_a->next;
			pre_a->next = b;
		}
	}
}

LinkList LinkListInverted(LinkList L) { //逆置链表
	if (L == NULL || L->next == NULL) {
		printf("Inverted Failed");
	}
	else {
		int length = ListLength(L);
		for (int i = 0; i < length / 2; i++) {
			ListNode_Swap(L, GetNode(L, i + 1), GetNode(L, length - i));
		}
		return L;
	}
}

LinkList LinkListInverted_a(LinkList L, LinkList L_inverted) {//逆置链表,但不使用原先的链表,创建一个新链表接收逆置的数据域,逆置的链表返回为L_inverted
	if (L == NULL || L->next == NULL || L_inverted == NULL) {
		printf("Inverted Failed");
	}
	else {
		LNode* p = L->next;
		while (p != NULL) {
			LNode* q = (LNode*)malloc(sizeof(LNode));
			q->data = p->data;
			q->next = L_inverted->next;
			L_inverted->next = q;
			p = p->next;
		}
		return L_inverted;
	}
}
void LinkListInverted(LinkList *L) { //逆置链表
    LNode* p = (*L)->next;
    LNode* q = p->next;
    (*L)->next = NULL;
    while (p != NULL) {
        p->next = (*L)->next;
        (*L)->next = p;
        p = q;
        if (q != NULL) {
            q = q->next;
        }
    }
}

void LinkListCopy(LinkList L, LinkList* L_copy) {//复制链表,使用前先创建一个新链表L_copy接收复制的数据域
	if (L == NULL || L->next == NULL || (*L_copy) == NULL) {
		printf("Can Not Copy!");
	}
	else {
		LNode* p = L->next;
		int i = 1;
		while (p != NULL) {
			ListInsert((*L_copy), i, p->data);
			p = p->next;
			i++;
		}

	}
}
#endif // !_LinkList_H_


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值