线性表
1. 数据结构的划分
数据结构按逻辑划分为线性结构与非线性结构。
1.1 线性结构
有限的序列;序列中的每一个元素都有唯一的前驱和后继,除了开头和结尾两个节点。
线性表主要由顺序表或链表表示。在实际应用中,常以栈、队列、字符串等特殊形式使用。
它们共同的特点就是数据之间的线性关系,除了头结点和尾结点之外,每个结点都有唯一的前驱和唯一的后继,也就是所谓的一对一的关系。
1.11 顺序表
顺序表中每个元素被分配的内存空间是连续的,我们的编程语言中的数组就是顺序表。
优点:无须为元素逻辑关系而增加额外的存储空间,可以快速取其中某个元素。
缺点:
- 顺序表的长度相对固定,当定义长度过大,有可能会浪费空间,过小则可能发生溢出。
- 插入和删除元素时,如果要保持有序的话,需要移动大量的数据。
1.12 链表
内存是不连续的,各个元素分配的内存不一样,内存和内存之间用指针进行相连,内存空间可以动态分配,解决了顺序表的缺点。
1.2 非线性结构
对应于线性结构,非线性结构也就是每个结点可以有不止一个直接前驱和直接后继。常见的非线性结构包括:树、图等。
2. 单链表
(线性链式表,简称链表,特点是用一组任意的存储单元存储线性表的数据元素。因此为了表示当前元素和逻辑上的下一个元素之间的关系,除了存储本身的数据之外,还要存储一个指示其直接后继的信息。由这两个信息组成的单元叫做结点。)
所以链表需要定义一个数据域和一个指针域,指针域用于存放逻辑上的下一个节点的地址,即指向下一个节点。
头节点的数据域存储了节点的个数(除去头节点和尾节点),指针域指向链表的第一个节点。
2.1 单链表数据节点类型的定义
typedef struct LinkListNode
{
ElemType data; /* 数据域存储数据 */
struct LinkListNode *next; /* 指针域存储下一个节点的地址 */
}LLNode;
其中宏定义ElemType便于修改元素类型,data是数据域,next是指针域。此处我们定义了一个next指针,而没有pre,指向上一个节点的指针,因此是单向链表(单链表),链表最后的节点指向NULL。
2.2 初始化单链表
Status initlist(LLNode *L)
{
if(!L)
{
printf("%s Invalid input argument\n",__func__);
return ERROR;
}
L->data = 0; /* 头节点的数据域记录链表中有多少个节点,现在为0,即一个都没有 */
L->next = NULL;
return SUCCESS;
}
链表的初始化时代表了头节点的初始化。
2.3 头插法
/* 在链表头插入新的节点 */
Status HeadInsert(LLNode *L,ElemType data)
{
if(!L)
{
printf("%s Invalid input argument\n",__func__);
return ERROR;
}
LLNode *newnode = (LLNode *)malloc(sizeof(LLNode));
newnode->data = data; /* 把数据放入新的节点的数据域中 */
newnode->next = L->next; /* 新的节点指向头结点的下一个节点(第一个节点) */
L->next = newnode; /* 头结点指向新插入的节点 */
L->data ++; /* 头结点的数据域+1,表示有新的一个节点插入 */
return SUCCESS;
}
在链表的表头插入新的节点,让头节点指向新的节点。
2.4 尾插法
/* 在链表尾插入新的节点 */
Status TailInsert(LLNode *L,ElemType data)
{
LLNode *cur;
if(!L)
{
printf("%s Invalid input argument\n",__func__);
return ERROR;
}
LLNode *newnode = (LLNode *)malloc(sizeof(LLNode));
newnode->data = data; /* 把数据放入新的节点的数据域中 */
newnode->next = NULL; /* 新的节点指向NULL(尾节点) */
cur = L;
while(cur->next) /* 遍历到最后一个节点 */
{
cur = cur->next;
}
cur->next = newnode; /* 链表尾部插入新节点 */
L->data ++; /* 头结点的数据域+1,表示有新的一个节点插入 */
return SUCCESS;
}
在链表的尾部插入新的节点,让新节点指向NULL。
2.5 判空
/* 判断是否为空链表 */
Status JudgeNull(LLNode *L)
{
if(!L)
{
printf("%s Invalid input argument\n",__func__);
return ERROR;
}
if(0 == L->data || NULL == L->next) /* 链表头结点的数据域为记录多少个实际节点 */
{
printf("LinkList is NULL\n");
return TRUE;
}
return FALSE;
}
判断链表是否为空链表,防止对空指针进行删除等操作,会导致段错误,主要是利用了头结点的特性,头结点记录了链表有多少个实际节点。
2.6 打印链表的全部数据
/* 遍历打印链表中所有的数据 */
Status PrintfList(LLNode *L)
{
LLNode *cur;
Status rv;
if(!L)
{
printf("%s Invalid input argument\n",__func__);
return ERROR;
}
rv = JudgeNull(L);
if(rv == TRUE || rv == ERROR) /* 栈空或者出错 */
return ERROR;
cur = L->next;
while(cur)
{
printf("%d->",cur->data);
cur = cur->next;
}
printf("NULL\n");
return SUCCESS;
}
打印前先判空,链表不为空的情况下,打印数据。
2.7 头删法
/* 从链表的头部删除一个节点 */
Status HeadDelete(LLNode *L)
{
Status rv;
if(!L)
{
printf("%s Invalid input argument\n",__func__);
return ERROR;
}
rv = JudgeNull(L);
if(rv == TRUE || rv == ERROR) /* 栈空或者出错 */
return ERROR;
LLNode *cur = L->next;
L->next = cur->next; /* 让头结点指向原来第一个节点的后一个节点,即删除了原来第一个节点 */
L->data --; /* 头结点的数据域-1,表示有新的一个节点删除 */
free(cur); /* 删除后释放内存 */
return SUCCESS;
}
让头结点指向原来第一个节点的后一个节点,并且释放掉第一个节点的内存空间,即删除了原来第一个节点。
2.8 尾删法
/* 从链表的尾部删除一个节点 */
Status TailDelete(LLNode *L)
{
Status rv;
LLNode *cur; /* 表示当前节点 */
LLNode *pre; /* 表示前一个节点 */
if(!L)
{
printf("%s Invalid input argument\n",__func__);
return ERROR;
}
rv = JudgeNull(L);
if(rv == TRUE || rv == ERROR) /* 栈空或者出错 */
return ERROR;
pre = L;
cur = pre->next;
while(cur->next) /* 遍历到最后一个节点 */
{
pre = cur;
cur = pre->next;
}
pre->next = NULL;
free(cur); /* 释放内存空间 */
L->data --;
return SUCCESS;
}
把链表最后一个实际节点删除,前一个节点指向NULL,并且释放掉最后一个实际节点的内存空间。
2.9 删除链表中数据域 data 的节点
/* 删除链表中数据域 data 的节点 */
Status ApoDelete(LLNode *L,ElemType data)
{
Status rv;
LLNode *cur; /* 表示当前节点 */
LLNode *pre; /* 表示前一个节点 */
int i = 0;
if(!L)
{
printf("%s Invalid input argument\n",__func__);
return ERROR;
}
rv = JudgeNull(L);
if(rv == TRUE || rv == ERROR) /* 栈空或者出错 */
return ERROR;
pre = L;
cur = pre->next;
while(cur) /* 遍历到最后一个节点 */
{
if(data == cur->data)
{
pre->next = cur->next;
free(cur);
i++;
cur = pre->next; /* 让cur指向删除节点的下一个节点,继续判断是否还有满足条件的节点 */
continue;
}
pre = cur;
cur = pre->next;
}
L->data -= i;
return SUCCESS;
}
删除链表中任意一个或多个数据域为data的节点。
2.10 清空链表
/* 清除链表 */
Status ClearList(LLNode *L)
{
Status rv;
LLNode *cur; /* 表示当前节点 */
LLNode *temp; /* 表示当前节点 */
if(!L)
{
printf("%s Invalid input argument\n",__func__);
return ERROR;
}
rv = JudgeNull(L);
if(rv == TRUE || rv == ERROR) /* 栈空或者出错 */
return ERROR;
cur = L->next;
while(cur)
{
temp = cur;
cur = cur->next;
free(temp);
}
L->data = 0;
L->next = NULL;
return SUCCESS;
}
清除链表中的全部实际节点。
整体代码实现
/*********************************************************************************
* Copyright: (C) 2022 Li Rongquan<2962837290@qq.com>
* All rights reserved.
*
* Filename: LinkList.c
* Description: This file is LinkList ctl.
*
* Version: 1.0.0(08/15/2022)
* Author: Li Rongquan <2962837290@qq.com>
* ChangeLog: 1, Release initial version on "08/15/2022 01:21:16 AM"
*
********************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#define SUCCESS 0
#define ERROR -1
#define TRUE 1
#define FALSE 0
typedef int Status; /* Status 是函数的类型,其值是函数结果状态代码,如OK等 */
typedef int ElemType; /* ElemType类型根据实际情况而定,这里取int整型 */
typedef struct LinkListNode
{
ElemType data; /* 数据域存储数据 */
struct LinkListNode *next; /* 指针域存储下一个节点的地址 */
}LLNode;
//typedef struct LinkListNode *LinkList; /* 定义一个别名Linklist(指针)
Status initlist(LLNode *L)
{
if(!L)
{
printf("%s Invalid input argument\n",__func__);
return ERROR;
}
L->data = 0; /* 头节点的数据域记录链表中有多少个节点,现在为0,即一个都没有 */
L->next = NULL;
return SUCCESS;
}
/* 在链表头插入新的节点 */
Status HeadInsert(LLNode *L,ElemType data)
{
if(!L)
{
printf("%s Invalid input argument\n",__func__);
return ERROR;
}
LLNode *newnode = (LLNode *)malloc(sizeof(LLNode));
newnode->data = data; /* 把数据放入新的节点的数据域中 */
newnode->next = L->next; /* 新的节点指向头结点的下一个节点(第一个节点) */
L->next = newnode; /* 头结点指向新插入的节点 */
L->data ++; /* 头结点的数据域+1,表示有新的一个节点插入 */
return SUCCESS;
}
/* 判断是否为空链表 */
Status JudgeNull(LLNode *L)
{
if(!L)
{
printf("%s Invalid input argument\n",__func__);
return ERROR;
}
if(0 == L->data || NULL == L->next) /* 链表头结点的数据域为记录多少个实际节点 */
{
printf("LinkList is NULL\n");
return TRUE;
}
return FALSE;
}
/* 遍历打印链表中所有的数据 */
Status PrintfList(LLNode *L)
{
LLNode *cur;
Status rv;
if(!L)
{
printf("%s Invalid input argument\n",__func__);
return ERROR;
}
rv = JudgeNull(L);
if(rv == TRUE || rv == ERROR) /* 栈空或者出错 */
return ERROR;
cur = L->next;
while(cur)
{
printf("%d->",cur->data);
cur = cur->next;
}
printf("NULL\n");
return SUCCESS;
}
/* 在链表尾插入新的节点 */
Status TailInsert(LLNode *L,ElemType data)
{
LLNode *cur;
if(!L)
{
printf("%s Invalid input argument\n",__func__);
return ERROR;
}
LLNode *newnode = (LLNode *)malloc(sizeof(LLNode));
newnode->data = data; /* 把数据放入新的节点的数据域中 */
newnode->next = NULL; /* 新的节点指向NULL(尾节点) */
cur = L;
while(cur->next) /* 遍历到最后一个节点 */
{
cur = cur->next;
}
cur->next = newnode; /* 链表尾部插入新节点 */
L->data ++; /* 头结点的数据域+1,表示有新的一个节点插入 */
return SUCCESS;
}
/* 从链表的头部删除一个节点 */
Status HeadDelete(LLNode *L)
{
Status rv;
if(!L)
{
printf("%s Invalid input argument\n",__func__);
return ERROR;
}
rv = JudgeNull(L);
if(rv == TRUE || rv == ERROR) /* 栈空或者出错 */
return ERROR;
LLNode *cur = L->next;
L->next = cur->next; /* 让头结点指向原来第一个节点的后一个节点,即删除了原来第一个节点 */
L->data --; /* 头结点的数据域-1,表示有新的一个节点删除 */
free(cur); /* 删除后释放内存 */
return SUCCESS;
}
/* 从链表的尾部删除一个节点 */
Status TailDelete(LLNode *L)
{
Status rv;
LLNode *cur; /* 表示当前节点 */
LLNode *pre; /* 表示前一个节点 */
if(!L)
{
printf("%s Invalid input argument\n",__func__);
return ERROR;
}
rv = JudgeNull(L);
if(rv == TRUE || rv == ERROR) /* 栈空或者出错 */
return ERROR;
pre = L;
cur = pre->next;
while(cur->next) /* 遍历到最后一个节点 */
{
pre = cur;
cur = pre->next;
}
pre->next = NULL;
free(cur); /* 释放内存空间 */
L->data --;
return SUCCESS;
}
/* 删除链表中数据域 data 的节点 */
Status ApoDelete(LLNode *L,ElemType data)
{
Status rv;
LLNode *cur; /* 表示当前节点 */
LLNode *pre; /* 表示前一个节点 */
int i = 0;
if(!L)
{
printf("%s Invalid input argument\n",__func__);
return ERROR;
}
rv = JudgeNull(L);
if(rv == TRUE || rv == ERROR) /* 栈空或者出错 */
return ERROR;
pre = L;
cur = pre->next;
while(cur) /* 遍历到最后一个节点 */
{
if(data == cur->data)
{
pre->next = cur->next;
free(cur);
i++;
cur = pre->next; /* 让cur指向删除节点的下一个节点,继续判断是否还有满足条件的节点 */
continue;
}
pre = cur;
cur = pre->next;
}
L->data -= i;
return SUCCESS;
}
/* 清除链表 */
Status ClearList(LLNode *L)
{
Status rv;
LLNode *cur; /* 表示当前节点 */
LLNode *temp; /* 表示当前节点 */
if(!L)
{
printf("%s Invalid input argument\n",__func__);
return ERROR;
}
rv = JudgeNull(L);
if(rv == TRUE || rv == ERROR) /* 栈空或者出错 */
return ERROR;
cur = L->next;
while(cur)
{
temp = cur;
cur = cur->next;
free(temp);
}
L->data = 0;
L->next = NULL;
return SUCCESS;
}
int main(int argc, char **argv)
{
LLNode L;
Status rv = 0;
int i;
ElemType insert_data = 0;
ElemType del_data = 0;
rv = initlist(&L);
if(rv != SUCCESS)
{
printf("Init list failure\n");
return -1;
}
printf("after Init list length is: %d\n",L.data);
for(i=1; i<=10; i++)
{
insert_data = i;
if(HeadInsert(&L,insert_data) != SUCCESS)
{
printf("HeadInsert error\n");
return -2;
}
}
printf("插入数据后的链表为:\n");
if(PrintfList(&L) != SUCCESS)
{
printf("Printf List faliure\n");
return -3;
}
printf("after HeadInsert list length is: %d\n",L.data);
for(i=1; i<=10; i++)
{
insert_data = i;
if(TailInsert(&L,insert_data) != SUCCESS)
{
printf("TailInsert failure\n");
return -2;
}
}
printf("插入数据后的链表为:\n");
if(PrintfList(&L) != SUCCESS)
{
printf("Printf List faliure\n");
return -3;
}
printf("after TailInsert list length is: %d\n",L.data);
if(HeadDelete(&L) != SUCCESS)
{
printf("HeadDelete List faliure\n");
return -4;
}
printf("从链表头部删除数据后的链表为:\n");
if(PrintfList(&L) != SUCCESS)
{
printf("Printf List faliure\n");
return -3;
}
printf("after HeadDelete list length is: %d\n",L.data);
if(TailDelete(&L) != SUCCESS)
{
printf("TailDelete List faliure\n");
return -5;
}
printf("从链表尾部删除数据后的链表为:\n");
if(PrintfList(&L) != SUCCESS)
{
printf("Printf List faliure\n");
return -6;
}
printf("after TailDelete list length is: %d\n",L.data);
del_data = 1;
if(ApoDelete(&L,del_data) != SUCCESS)
{
printf("OneDelete List faliure\n");
return 6;
}
printf("从链删除某一个数据后的链表为:\n");
if(PrintfList(&L) != SUCCESS)
{
printf("Printf List faliure\n");
return -6;
}
printf("after OneDelete list length is: %d\n",L.data);
del_data = 2;
if(ApoDelete(&L,del_data) != SUCCESS)
{
printf("OneDelete List faliure\n");
return -6;
}
printf("从链删除某一个数据后的链表为:\n");
if(PrintfList(&L) != SUCCESS)
{
printf("Printf List faliure\n");
return -7;
}
printf("after OneDelete list length is: %d\n",L.data);
if(ClearList(&L) != SUCCESS)
{
printf("ClearList List faliure\n");
return -8;
}
printf("清除链表后的链表为:\n");
if(PrintfList(&L) != SUCCESS)
{
printf("Printf List faliure\n");
return -7;
}
printf("after ClearList list length is: %d\n",L.data);
return 0;
}
```