数据结构(C语言)代码实现(六)——单链表的实现

目录

参考、格式

头文件LinkList.h

一、将函数的小括号写成中括号

二、读取权限冲突

三、L->Last指针没有移动

四、函数指针的使用

头文件完整代码

测试函数(主函数)test.cpp

测试结果


参考、格式

数据结构课本2.3节(严蔚敏版)

头文件LinkList.h

由于本部分函数过多,这里只介绍自己在实现过程中出现的问题。

一、将函数的小括号写成中括号

E0254 不允许使用类型名-CSDN博客

二、读取权限冲突

C++读取访问权限冲突引发异常问题_引发了异常: 读取访问权限冲突。-CSDN博客

没有记录错误代码,不过编码过程中多次出现过这个问题,原因是指针未初始化(特别是函数内部)

三、L->Last指针没有移动

复制代码
//算法2.20 插入元素 改编自算法2.9
Status List_Insert_L(LinkList& L, int i, ElemType e) {
	//在带头结点的单链线性表L的第i个元素之前插入元素e,1<=i<=L->len
	//更准确的说法是在第i个结点之后(包括头结点)插入结点。
	Link s = NULL, h = NULL;
	if (!LocatePos(L, i - 1, h))return ERROR;//i值不合法
	if (!MakeNode(s, e))return ERROR;//结点存储分配失败
	InsFirst(h, s);//对于从第i个结点开始的链表,第i-1个结点是它的头结点
	//基于课本中的这句注释,我觉得要实现的线性链表头结点和首元结点不一致
	if (i > L->len)L->tail = s;//若增加的结点位于尾部,应修改尾指针
	L->len++;//InsFirst函数没有让记录元素个数的变量增加
	return OK;
}

四、函数指针的使用

函数指针和指针函数用法和区别-CSDN博客

头文件完整代码

复制代码
#pragma once
#include <cstdio>
#include <cstdlib>
#include <cstring>

#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define INFEASIBLE -1
#define OVERFLOW -2
typedef int Status;//Status是函数的类型,其值是函数结果状态代码
typedef int ElemType;

//-----线性链表的存储结构---------
//-----实现单向非循环动态链表-----
//-----头结点与首元结点不一致-----
typedef struct LNode {//结点类型
	ElemType data;
	struct LNode* next;
}LNode,*Link,*Position;

typedef struct LinkNode{      //链表类型
	Link head, tail;  //分别指向线性链表中的头结点和最后一个结点
	int len;          //指示线性链表中数据元素的个数
}LinkNode,*LinkList;

Status MakeNode( Link & p,ElemType e ){
	//分配由p指向的值为e的结点,并返回OK;若分配失败,则返回ERROR
	p = (Link)malloc(sizeof(LNode));
	if (!p)
		return ERROR;
	p->data = e;
	return OK;
}
void FreeNode(Link& p) {//相当于free函数重命名
	//释放p所指结点
	free(p);
    p = NULL;
}

Status InitList(LinkList& L) {
	//构造一个空的线性链表L
	L = (LinkList)malloc(sizeof(LinkNode));
	if (!L)return ERROR;
	L->head = L->tail = (Link)malloc(sizeof(LNode));
	if (!L->head)return ERROR;
	L->head->data = 0;
	L->head->next = NULL;
	L->len = 0;
	return OK;
}

Status DestroyList(LinkList& L) {
	//销毁线性链表L,L不再存在
	Link p;
	while (L->head) {//删除所有结点
		p = L->head;
		L->head = L->head->next;
		free(p);
	}
	free(L);
	L = NULL;
	return OK;
}

Status ClearList(LinkList& L) {
	//将线性链表L重置为空表,并释放原链表的结点空间
	Link p = L->head;
	while (L->head->next) {//保留一个结点,让其作为头结点
		p = L->head->next;
		L->head->next = p->next;
		free(p);
	}
	L->tail = L->head;
	L->len = 0;
	return OK;
}

Status InsFirst(Link h, Link s) {
	//已知h指向线性链表的头结点,将s所指结点插入到首元结点之前
	s->next = h->next;
	h->next = s;
	//这个函数没修改L->len的值,课本算法2.20需要补充L->len++。
	return OK;
}

Status DelFirst(Link h, Link& q) {
	//已知h指向线性链表的头结点,删除链表中的首元结点并以q返回
	q = h->next;
	h->next = q->next;
	q->next = NULL;
	return OK;
}

Status Append(LinkList& L, Link s) {
	//将指针s所指(彼此以指针相链)的一串结点链接在线性链表L的最后一个结点(用L->tail寻找)
	//之后,并改变链表L的尾指针指向新的尾结点
	while (s) {
		L->tail->next = s;
		L->tail = s;
		L->len++;
		s = s->next;
	}
	return OK;
}

Status Remove(LinkList& L, Link& q) {
	//删除线性链表L中的尾节点并以q返回,改变链表L的尾指针指向新的尾结点
	if (!L->tail)return ERROR;
	Link p = L->head;
	while (p->next != L->tail)
		p = p->next;//寻找尾结点的上一个结点
	q = L->tail;
	L->tail = p;//改变尾结点
	L->tail->next = NULL;
	L->len--;
	return OK;
}

Status InsBefore(LinkList& L, Link& p, Link s) {
	//已知p指向线性链表L中的一个结点,将s所指结点插入在p所指结点之前
	//并修改指针p指向新插入的结点
	Link q = L->head;
	while (q->next != p)
		q = q->next;//寻找结点q的上一个结点
	q->next = s;
	s->next = p;//插入s
	p = s;//修改指针p
	L->len++;
	return OK;
}

Status InsAfter(LinkList& L, Link& p, Link s) {
	//已知p指向线性链表L中的一个结点,将s所指的结点插入在p所指结点之后
	//并修改指针p指向新插入的结点
	s->next = p->next;
	p->next = s;
	p = s;
	L->len++;
	return OK;
}

Status SetCurElem(Link& p, ElemType e) {
	//已知p指向线性链表L中的一个结点,用e更新p所指结点中数据元素的值
	p->data = e;
	return OK;
}

ElemType GetCurElem(Link p) {
	//已知p指向线性链表L中的一个结点,返回p所指结点中数据元素的值
	return p->data;
}

Status ListEmpty(LinkList L) {
	//若线性链表L为空表,则返回TRUE,否则返回FALSE
	if (!L->len)
		return TRUE;
	else
		return FALSE;
}

int ListLength(LinkList L) {
	//返回线性链表L中元素个数
	return L->len;
}

Position GetHead(LinkList L) {
	//返回线性链表L中头结点的位置
	return L->head;
}

Position GetLast(LinkList L) {
	//返回线性链表L中尾结点的位置
	return L->tail;
}

Position PriorPos(LinkList L, Link p) {
	//已知p指向线性链表L中的一个结点,返回p所指结点中的直接前驱的位置
	//若无前驱,则返回NULL
	Link q = L->head;
	if (p == L->head)return NULL;
	while (q->next != p)
		q = q->next;
	return q;
}

Position NextPos(LinkList L, Link p) {
	//已知p指向线性链表L中的一个结点,返回p所指结点中的直接后继的位置
	//若无后继,则返回NULL
	return p->next;
}

Status LocatePos(LinkList L, int i, Link& p) {
	//返回p指示线性链表L中第i个结点的位置并返回OK,i值不合法时返回ERROR
	//头结点当作第0结点,首元结点第1个,尾结点第(L->len)个
	if (i<0 || i>L->len)return ERROR;
	int j = 0;Link q = L->head;
	while (j < i) {
		q = q->next;
		++j;//j++也一样
	}
	p = q;
	return OK;
}

//compare与visit都是函数指针,请看相关博客
Position LocateElem(LinkList L, ElemType e, Status(*compare)(ElemType, ElemType)) {
	//返回线性链表L中第1个与e满足函数compare()判定关系的元素的位置,
	//若不存在这样的元素,则返回NULL
	int i = 1;Link p = L->head->next;
	while (i <= L->len && !(*compare)(p->data, e)) {
		p = p->next;
		i++;
	}
	return p;
}

//遍历链表
Status ListTraverse(LinkList L, void (*visit)(Link)) {
	//依次对L的每个元素调用函数visit()。一旦visit()失败,则操作失败。
	Link p = L->head;
	for (int i = 1;i <= L->len;i++) {
		p = p->next;
		visit(p);
	}
	printf("\n");
	return OK;
}
//上面的为线性链表的基本算法

//算法2.20 插入元素 改编自算法2.9
Status List_Insert_L(LinkList& L, int i, ElemType e) {
	//在带头结点的单链线性表L的第i个元素之前插入元素e,1<=i<=L->len
	//更准确的说法是在第i个结点之后(包括头结点)插入结点。
	Link s = NULL, h = NULL;
	if (!LocatePos(L, i - 1, h))return ERROR;//i值不合法
	if (!MakeNode(s, e))return ERROR;//结点存储分配失败
	InsFirst(h, s);//对于从第i个结点开始的链表,第i-1个结点是它的头结点
	//基于课本中的这句注释,我觉得要实现的线性链表头结点和首元结点不一致
	if (i > L->len)L->tail = s;//若增加的结点位于尾部,应修改尾指针
	L->len++;//InsFirst函数没有让记录元素个数的变量增加
	return OK;
}

//删除元素
Status List_Delete_L(LinkList& L, int i, ElemType& e) {
	Link s = NULL, h = NULL;
	if (!LocatePos(L, i - 1, h))return ERROR;//i值不合法
	if (i == L->len)L->tail = h;//若删除的结点位于尾部,应修改尾指针
	DelFirst(h, s);//对于从第i个结点开始的链表,第i-1个结点是它的头结点
	e = s->data;
	FreeNode(s);
	L->len--;
	return OK;
}

//算法2.21 合并线性表 改编自算法2.12
Status MergeList_L(LinkList& La, LinkList& Lb, LinkList& Lc, int (*compare)(ElemType, ElemType)) {
	//已知单链线性表La和Lb的元素按值非递减排列。
	//归并La和Lb得到新的单链线性表Lc,Lc的元素也按值非递减排列。
	if (!InitList(Lc))return ERROR;//存储空间分配失败
	Link ha = GetHead(La);Link hb = GetHead(Lb);//ha和hb分别指向La和Lb的头结点
	Link pa = NextPos(La, ha);Link pb = NextPos(Lb, hb);//pa和pb分别指向La和Lb中当前结点
	while (pa && pb) {//La和Lb均非空
		int a = GetCurElem(pa);int b = GetCurElem(pb);//a和b为两表中当前比较元素
		Link q = NULL;
		if ((*compare)(a, b) <= 0) {//a<=b
			DelFirst(ha, q);Append(Lc, q);pa = NextPos(La, ha);
		}
		else{//a>b
			DelFirst(hb, q);Append(Lc, q);pb = NextPos(Lb, hb);
		}
	}
	if (pa)Append(Lc, pa);//链接La中剩余结点
	else Append(Lc, pb);//链接Lb中剩余结点
	FreeNode(ha);FreeNode(hb);//释放La和Lb的头结点
	return OK;
}

测试函数(主函数)test.cpp

InBefore、InAfter、PriorPos这三个函数没有测试。

复制代码
#include "LinkList.h"
Status comp(ElemType c1, ElemType c2) /* 数据元素判定函数(平方关系) */
{
    if (c1 == c2 * c2)
        return TRUE;
    else
        return FALSE;
}

int compare(ElemType a, ElemType b) /*数据元素判定函数(大小关系)*/
{
    return a - b;
}

void visit(Link p) /* ListTraverse()调用的函数(类型要一致) */
{
    printf("%d ", p->data);
}

void dbl(Link p) /* ListTraverse()调用的另一函数(元素值加倍) */
{
    p->data *= 2;
}

int main()
{
    LinkList L, L1, L2;
    Status i;
    ElemType e0;
    int j;
    Link p, q;
    i = InitList(L);
    printf("初始化L后:L->len=%u\n", L->len);
    for (j = 1;j <= 5;j++) {
        i = List_Insert_L(L, 1, j);//有没有i=不影响结果,只有List_Insert(L,1,j);也行。
    }
    printf("在表头插入元素1到5,表L中元素为");
    i = ListTraverse(L, visit);
    i = ClearList(L);
    i = ListEmpty(L);
    if (i)
        printf("表L为空\n");
    for (j = 10;j > 5;j--) {
        i = List_Insert_L(L, 1, j);
    }
    printf("表L中元素为");
    i = ListTraverse(L, visit);
    printf("表L中元素个数为%d", ListLength(L));
    i = ListTraverse(L, dbl);
    printf("表L中元素为");
    i = ListTraverse(L, visit);
    i = List_Delete_L(L, 3, e0);
    printf("被删除的元素为%d\n", e0);
    printf("表L中元素为");
    i = ListTraverse(L, visit);
    for (j = 10;j > 5;j--) {
        i = List_Insert_L(L, 1, j);
    }
    printf("表L中元素为");
    i = ListTraverse(L, visit);
    printf("表L中元素个数为%d\n", ListLength(L));
    i = Remove(L, q);
    printf("被删除的元素为%d\n", q->data);
    i = SetCurElem(L->tail, 30);
    printf("表L中元素为");
    i = ListTraverse(L, visit);
    for (j = 3;j <= 4;j++) {
        p = LocateElem(L, j, comp);
        if (p)
            printf("表L中第1个为%d的平方的元素是%d\n", j, p->data);
        else
            printf("表L中没有%d的平方的元素\n", j);
    }
    i = InitList(L1);
    for (j = 13;j >= 3;j -= 2) {
        i = List_Insert_L(L1, 1, j);
    }
    printf("表L1中元素为");
    i = ListTraverse(L1, visit);
    i = MergeList_L(L, L1, L2, compare);
    printf("表L2中元素为");
    i = ListTraverse(L2, visit);//此时L与L1头结点均被删除
    i = DestroyList(L2);
	return 0;
}

测试结果

相关推荐
Scott9999HH4 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
码智社5 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海5 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖6 小时前
JDK 26 新特性详解
java·开发语言
马优晨6 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区8 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大9 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai9 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
এ慕ོ冬℘゜9 小时前
前端基础:什么是时间戳?JS获取时间戳三种方法与实战用途
开发语言·前端·javascript
执明wa9 小时前
LayoutInflater详解: XML是如何变成View的?
android·xml·开发语言·android studio