C语言数据结构 — 链式线性表基础理解(从指针&结构体到单链表)

本文适用于学了C语言基础,但由于对指针,函数,结构体等知识理解不够透彻,被数据结构课程开门暴击的同学(是我本人)。本文将结合C语言基础知识,详细解释单链表存储结构及基本操作。本人所用的教材是清华大学出版社的《C语言数据结构》,书中多为c++代码,但课程要求所有作业考试的代码均使用C语言,故而本文将使用C语言(改编自课本代码)。

目录

        零. 指针相关知识点复习

1.指针的基础理解

2.指针与函数

3.指针与结构体

一. 线性表的链式表示

二. 存储结构

三. 基本操作

1. 初始化:创建带头节点的空链表

2. 查找:要获取第i个元素,必须从头节点开始

3. 插入操作:  -->  , ,>,>,>

4. 删除

5. 销毁

OK啦~ 从C语言基础到数据结构入门就结束啦~

附:有 test 和所有基本操作函数的完整代码


零. 指针相关知识点复习

1.指针的基础理解

先看一段非常简单的代码:

int a=10;
int* p;  //声明一个指向整型数据的指针
p=&a;    //p的存储单元里写的是a的地址(也就是说指针p指向a)
         //这两行可以合起来写成: int* p=&a;
printf(" a=%d, &a=%x\n",a, &a);            //输出a的值和地址
printf("*p=%d,  p=%x, &p=%x", *p, p, &p);  //输出指针p指向的存储地址中的数值,p的值,p自身地址
	

运行结果:

详细解释(很重要):

int* p 声明了一个指向整型的指针;p=&a 代表着将变量a的存储地址赋给p;*p代表着指向地址为p值(65fe1c)的变量的值。

我们可以把每个存储单元看成一个抽屉,变量的值就是这个抽屉里放的东西,地址就是这个抽屉的编号标签;那么指针变量的“抽屉”同样也有自己的地址和存储内容,只是这个抽屉里放的是其他抽屉的编号标签。“*”代表“指向”,“&”代表取地址。

2.指针与函数

我们先来看一段代码:

一级指针:

bool ValueChange(int* add){
	printf("add=%x  (*add)=%d",add,*add);
	*add=(*add)+1;
	return true;
}

int main() {
	int a=2;
    int* p=&a;
	printf("a=%d\n",a);
	ValueChange(p); //传入a的地址
	printf("\nAfter change, a=%d", a); 
	return 0;
}

我们传入函数的是变量a的地址,而非a的值,所以函数不是在对参数值进行操作,而是对处于“65felc”这一存储位置的变量本身进行操作。

我们再来看看二级指针

typedef int* IntPointer;

bool ValueChange(int* add){
	printf("add=%x  (*add)=%d",add,*add);
	*add=(*add)+1;
	return true;
}

void PointerChange(IntPointer *ph, int* qh){
    printf("\nph=%x, (*ph)=%x, *(*ph)=%d",ph,*ph,*(*ph));
    //ph是指针自己的存储地址,*ph是指针的存储内容(即另一变量的地址),*(*ph)就是存储变量的值
    *ph = qh;//改变p的指向对象
	
	printf("\nafter change:");
	printf("\nph=%x, (*ph)=%x, *(*ph)=%d",ph,*ph,*(*ph));
}

int main() {
	/*一级指针*/
    printf("1:");
	int a=2;
	IntPointer p=&a;//等价于 int* p=&a;
	printf("a=%d\n",a);
	ValueChange(p);
	printf("\nAfter change, a=%d", a);
	
	/*二级指针*/
    printf("\n\n2:");
	int b=6;
	IntPointer q=&b;
	printf("\n&a=%x,a=%d",&a,a);
	printf("\n&b=%x, b=%d",&b,b);
    PointerChange(&p,q);
    printf("\nafter PointerChange:");
	printf("\np=%x, (*p)=%d",p,*p);

	return 0;
}

        注意:ph是指针自己的存储地址,*ph是指针的存储内容(即另一变量的地址),*(*ph)就是存储变量的值。

        在第一段代码中(ValueChange函数及/*一级指针*/之后的代码),我们将变量a的地址传入ValueChange函数,在函数中,我们改变了变量a的存储内容(即*add的值)。

        而在这段代码中(PointerChange函数及/*二级指针*/之后的代码),我们将指针p的地址,及变量b的地址传入PointerChange函数,在函数中,我们将p的存储内容(即*ph 的值)改为了b的地址(65fe04)。

        通过这两段代码可以看出,一级指针用于指向的变量进行操作(例如:改变指向变量的值);二级指针用于对指针本身进行操作(例如:改变指向对象,由指向a改为指向b)。

3.指针与结构体

结构体相当于自定义的数据类型,在理解结构体指针时,可以类比指向int型数据指针。后文存储结构标题下会有详细解释。

一. 线性表的链式表示

线性表的链式表示:用一组任意的存储单元(可以连续也可以不连续)存储线性表的数据元素

数据元素会在数据域存储其本身信息,在指针域存储其直接后继的存储位置

数据域

data

指针域

next

二. 存储结构

typedef int ElemType;//以存储数据是int型为例

typedef struct LNode{
	ElemType data;      //数据
	struct LNode* next; //指针
}LNode, *Linklist;      //LNode *L  等价于 Linklist L

1.struct LNode* next:int* p 代表着声明一个指向整型变量的指针变量,同理,struct LNode* next 则代表着声明一个指向LNode这个结构体类型的指针变量。

2.LNode 与 LinkList:将来我们要声明一个指向LNode类型的指针,我们可以写 LNode* pL;也可以直接写成 LinkList pH;      LNode *L  等价于 Linklist L

3.存储结构图解:

三. 基本操作

1. 初始化:创建带头节点的空链表

main 函数中:

Linklist L;
InitList(&L); //将L的地址传入函数

初始化函数:

void InitList(Linklist *pL) //相当于LNode* *L,二级指针可以修改指针本身
{
	(*pL)=(Linklist)malloc(sizeof(LNode));//给头结点分配内存空间
	if(!(*pL)) exit(1);
	(*pL)->next=NULL;//头指针指向头结点 头结点下一位为空
}

注意:初始化需要对指针本身进行修改,故我们使用到二级指针

2. 查找:要获取第i个元素,必须从头节点开始

注意:查找不需要对指针本身进行改动,故只用到了一级指针

bool GetElem(Linklist L, int pos, ElemType* e)//不需要对L本身进行改动
{
	Linklist p=L;
	int j=0;
	while(p&&j<pos)//令p指向第pos个结点
	{
		p=p->next; j++;
	}
	if(!p) return false;
	*e=p->data; return true;
}

3. 插入操作:<ai, ai+1>  -->  <ai, e>, <e, ai+1>

bool ListInsert(Linklist* pL, int pos, ElemType e)//头指针,插入位置,插入内容
{
	Linklist q=*pL;int j=0;
	while(q&&j<pos-1)//令q指向第pos-1个结点
	{
		q=q->next;
		j++;
	}
	if(!q||j>pos-1){
		printf("q is empty? %d j=%d pos=%d\n",!q,j,pos);
		return false;
	}//参数不合法,pos<1或 pos>表长+1
	
	Linklist s=(Linklist)malloc(sizeof(LNode));
	if(!s) exit(1);
	s->data=e;//把新元素放进新结点
	
	s->next=q->next; q->next=s;//修改指针,插入结点
	return true;
}

step 1: 创建新节点

step 2: 插入新节点

        1)令工作指针 q 指向第 pos-1 个节点   

              (主要讲为啥是 j < pos-1, 非常理解  while 循环的可以不看)

        2)修改指针,插入节点

4. 删除

bool ListDelete(Linklist* pL, int pos, ElemType* e)//第pos个元素,用指针e带回
{
	Linklist p= *pL; 
	int j=0;
	while(p->next&&j<pos-1) //令p指向第pos-1个节点
	{
		p=p->next;
		j++;
	}
	if(!(p->next)||j>pos-1) return false;//参数不合法,pos<1或 pos>表长+1
	
	Linklist q=p->next; p->next=q->next;//修改指针
	
	*e=q->data; 
	free(q);//释放结点空间
	return true;
}

删除与插入节点是反过程,可以对应起来一起看

step 1 令工作指针指向第pos-1个节点

step 2 修改指针

step 3 释放空间

5. 销毁

void DestroyList(Linklist *pL)//Linklist类型指向透支着
{
	Linklist q;
	while(*pL)//销毁以L为头指针的单链表,释放所有结点空间
	{
		q=*pL;
		*pL=(*pL)->next;
		free(q);
	}
	*pL=NULL;
}

OK啦~ 从C语言基础到数据结构入门就结束啦~

之后的内容无论怎么变化,都是基于对结构体,指针等基础知识的理解与应用,跨过这道坎后面的知识就会好理解一些些~

附:有 test 和所有基本操作函数的完整链表代码

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

typedef int ElemType;//以int为例

/*一.线性表的链式表示:存储单元可以连续也可以不连续;
					数据域(自身信息)+指针域(直接后继的存储位置)*/

/*二.存储结构*/
typedef struct LNode{
	ElemType data;      //数据
	struct LNode *next; //指针
}LNode, *Linklist;      //相当于struct LNode, struct LNode* 
				        //LNode *L  等价于 Linklist L, 可以用于二级指针
					
/*三.单链表基本操作:1.初始化 2.获取元素 3.插入元素 4.删除元素 5.销毁结构*/

//1.创建带头节点的空链表
void InitList(Linklist *pL) //相当于LNode* *L,二级指针可以修改指针本身
{
	(*pL)=(Linklist)malloc(sizeof(LNode));//给头结点分配内存空间
	if(!(*pL)) exit(1);
	(*pL)->next=NULL;//头指针指向头结点 头结点下一位为空
}

//2.销毁结构
void DestroyList(Linklist *pL)//Linklist类型指向透支着
{
	Linklist q;
	while(*pL)//销毁以L为头指针的单链表,释放所有结点空间
	{
		q=*pL;
		*pL=(*pL)->next;
		free(q);
	}
	*pL=NULL;
}

//3.获取元素:要获取第i个元素,必须从头节点开始
bool GetElem(Linklist L, int pos, ElemType* e)//不需要对L本身进行改动
{
	Linklist p=L;
	int j=0;
	while(p&&j<pos)//令p指向第pos个结点
	{
		p=p->next; j++;
	}
	if(!p) return false;
	*e=p->data; return true;
}

//4.插入元素:修改相应指针
bool ListInsert(Linklist* pL, int pos, ElemType e)
{
	Linklist s=(Linklist)malloc(sizeof(LNode));
	if(!s) exit(1);
	s->data=e;//把新元素放进新结点
	
	Linklist q=*pL;int j=0;
	while(q&&j<pos-1)//令q指向第pos-1个结点
	{
		q=q->next;
		j++;
	}
	if(!q||j>pos-1){
		printf("q is empty? %d j=%d pos=%d\n",!q,j,pos);
		return false;
	}//参数不合法,pos<1或 pos>表长+1
	
	s->next=q->next; 
	q->next=s;//修改指针,插入结点
	return true;
}

//5.删除元素
bool ListDelete(Linklist* pL, int pos, ElemType* e)//第pos个元素,用指针e带回
{
	Linklist p= *pL; 
	int j=0;
	while(p->next&&j<pos-1)
	{
		p=p->next;
		j++;
	}
	if(!(p->next)||j>pos-1) return false;//参数不合法,pos<1或 pos>表长+1
	
	Linklist q=p->next; 
	p->next=q->next;//修改指针

	*e=q->data; 
	free(q);//释放结点空间
	return true;
}

/************************************* Testing ****************************************/
int main() 
{
	Linklist L;//头指针(结构体指针),指向下一个结构体:LNode *L
	/*1.初始化*/
	InitList(&L);	
	
	//2.输入int类型数据,输入-1停止操作
	ElemType num; int i=1;
	printf("please enter the data end up with -1\n");
	do{
		scanf("%d",&num);
		if(num!=-1)
		{
			//printf("%d: %d\n",i,ListInsert(&L,i,num));
			ListInsert(&L,i,num);
		}
		i++;
	}while(num!=-1); 
	
	/*3.将数据9插入第三个节点,检测是否插入成功,并输出链表数据*/
	printf("Insert successfully?:%d\n",ListInsert(&L,3,9));
	Linklist q=L;
	printf("now the Linklist: ");
	for(i=0;q->next!=NULL;i++)
	{
		q=q->next;
		printf("%d ",q->data);
	}
	
	
	
	
	/*4.删除第四个节点,检测是否删除成功*/
	int E=1;
	int* e=&E;//指针要初始化;
	printf("\nDelete sucessfully?:%d",ListDelete(&L,4,e));
	if(ListDelete(&L,4,e)) printf(" The deleted num=%d\n",*e);//输出删除数据
	
	/*5.查找第三个节点数据*/
	printf("Get sucessfully?: %d", GetElem(L,3,e));
	if(GetElem(L,3,e)) printf("  The third num=%d\n",*e);//输出删除数据
	
	
	/*6.输出链表所有数据*/
	Linklist p=L;
	printf("now the Linklist: ");
	for(i=0;p->next!=NULL;i++)
	{
		p=p->next;
		printf("%d ",p->data);
	}
	
	/*7.销毁*/
	DestroyList(&L);
	
	return 0;
}

  • 5
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值