数据结构-线性链表(链式存储实现-C语言)
链式理解:
线性表的顺序存储结构的特点是逻辑上相邻的元素在物理存储(计算机内存)上也是相邻,可以计算任意一个元素的地址(原理与计算公式在顺序表实现章节),从而可以随机存取任意一个元素,但这种结构存在些弱点:在作插入和删除元素时,需要移动大量元素。链表却与之不同,链表是逻辑上相邻的元素在物理存储上不一定相邻,因而在作插入和删除元素时不需要移动大量元素,但同时也因为物理存储不相邻,所以失去了随机存取的优点。
名词介绍:
先理解几个概念:结点、头指针、头结点、首元结点
结点:线性链表中单个元素不仅存储本身的信息还存储一个指向其直接后继的信息 (即直接后继的存储位置,这在C中使用指针保存这个位置),这两部分信息组成在一起就是一个结点。结点中包含数据信息的部分叫数据域,包含地址信息的部分叫做指针域。
头指针:指向整个链表开始的一个结点(这个结点可能是头结点或者首元结点)的指针,一般用L表示。
首元结点:链表的第一个数据元素。
头结点:在首元结点之前增加一个结点,这个结点就是头结点。
![在这里插入图片描述](https://img-blog.csdnimg.cn/749522e5c1374a5a9c01086c445bac02.png#pic_center)
线性链表有两种结构:
第一种:带头结点(常用)
![在这里插入图片描述](https://img-blog.csdnimg.cn/7d5f0b0481eb40c782f55379d67957ea.png#pic_center)
第二种:不带头结点(不常用)
![在这里插入图片描述](https://img-blog.csdnimg.cn/437bb54601354cf1af44eeeed0794817.png#pic_center)
算法实现
首先定义线性表的单链表存储结构体
#include <stdio.h>
#include <stdlib.h>
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define INFEASIBLE -1
#define OVERFLOW -2
typedef int Status;
typedef char ElemType;//元素类型可以根据自己需求自定义类型,这里以char类型为例
typedef struct LNode{
ElemType data; //数据域
struct LNode *next; //指针域
} LNode,*LinkList;
(备注:1.以上代码供下方的算法实现使用。2.根据自己的需求可以在下面代码实现中添加自己的一些判空处理,增加代码严谨性)
说明:以下的线性链表均带头结点的,链表简单的操作不给予实现,只记录重要的几个操作)
**一、**初始化线性链表(或单链表)
Status InitList_L(LinkList &L){//为了操作方便,使用c++中的引用操作符"&"
L=(LinkList)malloc(sizeof(LNode));//内存中开辟一个和LNode结构体一样大小的空间,返回这个空间的首地址(头指针指向头结点)
(*L)->next=NULL;//链表为空
return OK;
}
**二、**销毁线性链表(或单链表):从头结点开始遍历,依次释放每一个结点
Status DestroyList_L(LinkList L){
// 可编写自己的判空处理逻辑,使代码严谨
LNode *p,*L1=L->next;
while(L1){//首元结点存在,则开始遍历
p=L1;
L1=L1->next;
free(p);
}
return OK;
}
**三、**查找单链表的第i个元素(按位置查找):因位置 i 值不同,分为三种情况:
1.如果i大于链表的元素个数,这时满足循环条件进行遍历直到p为NULL(这时j还是小于i)
2.如果i小于链表的元素个数(即i<1),这时循环条件不满足(j<i条件不满足)循环结束
3.如果1<= i <=链表元素个数(合法范围),循环进行到 j=i 时退出循环(这时p不为NULL),最终e=p->data执行,即第i个元素查找成功。
Status GetElem_L(LinkList L,int i,ElemType &e){
Lnode *p=L->next;
int j=1;
while(p&&j<i){
p=p->next;
++j;
}
if(!p || j>i) return ERROR;
e=p->data;
return OK;
}
**三-1、**按值查找,返回改值元素的地址
Lnode* LocateElem_L1(LinkList L,ElemType e){
Lnode *p=L->next;
while(p&&p->data!=e){
p=p->next;
}
return p;
}
**三-2、**按值查找、返回该值元素在链表中的位置(第几个元素,从1开始)
int LocateElem_L2(LinkList L,ElemType e){
Lnode *p=L->next;
int j=1;
while(p&&p->data!=e){
p=p->next;
j++;
}
if(p) return j;
else return 0;
}
**四、**向链表中第i个位置插入新的元素(要点:向第i个位置上插入一个元素,意味着要先找到第i个元素的前一个元素(记为pre),然后新建一个结点,使该节点的next指针等于pre->next,然后把pre的next指针指向新建的结点。)
Status ListInsert_L(LinkList L,int i,ElemType e){
Lnode *p=L->next,*s;
int j=0;
while(p&&j<i-1){//找到第i元素的前一个元素
p=p->next;
j++;
}
if(!p||j>i-1)return ERROR;
s=(LinkList)malloc(sizeof(Lnode));
s->next=p->next;
p->next=s;
return OK;
}
**五、**删除链表中第i个元素(要点:和插入的类似,先找到要删除的第i个元素的前一个元素§,然后元素p的next指针指向下一个元素的next所指向的元素(在指针指向之前先使用变量q保存下p的next所指向的结点)
然后使用free(q)释放要删除的结点即可
Status ListDelete_L(LinkList L,int i){
int j=0;
Lnode *p=L->next,*q;
while(p&&j<i-1){
p=p->next;
j++;
}
if(!p||j>i-1) return ERROR;
q=p->next;
p->next=p->next->next;
free(q);
return OK;
}
注:有关链表的操作还有许多,比如:头插法创建链表、尾插法创建链表、链表清空、计算链表长度、判断链表是否、合并两个单链表(两个非递减单链表合成为一个非递减单链表)等…
(本文仅仅是个人在学习数据结构中做的笔记,留作纪念)