本篇博客主要是对线性链表的基本实现,因此无法摆脱链表的基本缺点,比如无法快速定位前驱、无法快速确定元素个数等等。当然优点是能够快速对链表进行学习。本篇博客将网络上对单链表的各种操作进行实现,方便大家学习,如有错误,不吝指正!
一、概念
线性表的链式存储结构的特点使用一组任意的存储单元存储线性表的数据元素。包含两个域:数据域和指针域。
① n个节点离散分布,彼此通过指针相联系。
② 除头节点和尾节点外,每个节点只有一个前驱节点和一个后续节点。
③ 头节点没有前驱节点,尾节点没有后继节点。
注意:
头节点的数据类型和首节点类型一样,头节点并不存放有效数据,头结点的指针域存储指向第一个结点的指针(即第一个元素结点的存储位置)。
加头节点的目的是方便对链表的操作,比如在链表头部进行节点的删除、插入。
头指针:指向头节点的指针变量。这里以*head进行标识。
#define ElemType int
//--------线性表的单链表存储结构--------
typedef struct LNode
{
ElemType data;
struct LNode *next;
}LNode;
typedef LNode* LinkList;
二、函数实现的难点
由于本篇文章主要是学习单向链表的基本实现,因此这里将遇到的重难点记录下来,方便复习回顾。
//创建一个头结点
void InitList(LinkList *head)
该函数主要是创建一个头结点,和我们定义的数据结构对应。如果线性表为空表,则头结点的指针域为“空”。
也就是说,我们要明白头结点和第一个结点意义是不同的,头结点之后的结点是第一个结点。
//头插法建立单链表
void createListH(LinkList *head)
//尾插法建立单链表
void createListrR(LinkList *head)
在头结点建立的基础上,我们可以使用头插法或者尾插法进行链表的建立。头插法主要是在头结点和第一个结点直接插入新的结点;尾插法是在尾结点后插入新的结点。
//特定下标处插入值
bool insert(LinkList *head, int pos, ElemType val)
// 删除某个节点
bool listDelete(LinkList *head, int pos)
在某个特定下标处插入一个结点或者删除一个结点。插入值时需要找到pos-1处,将新的结点尾插;删除结点时找到pos-1处,使其指向pos的next,同时释放pos处结点。
其实单链表“位序”的概念已淡化,这里只是将其实现,加深对单链表的理解。
//1.判断非空 2.删除头结点之后第一个结点
void pop_front(LinkList *head)
//1.判断非空 2.删除倒数第一个结点
void pop_back(LinkList *head)
无论是尾删还是头删,首先要考虑链表是否为空,然后找到相应的位置删除结点。
通过上面四个函数的实现,我们可以发现插入和删除时,最重要的是找到指向想要位置的前驱,而并不是指向想要位置的指针。比如pop_back()函数,虽然我们想要删除尾结点,但最重要的是找到倒数第二个结点,这里我们通过一个临时变量寄存。
//单链表中数据节点的个数
int listLength(LinkList *head)
//链表是否为空
bool is_empty(LinkList *head)
//升序排序
void sort(LinkList *head)
//查找值
LNode* find(LinkList *head, ElemType key)
//仅显示,不修改
void showList(LinkList head)
这些函数都很简单,这里都不详述。
综上,我们可以看到,单链表的优势在于插入和删除。如果实现随机访问,元素个数等,链表就要诸多不便了。
三、代码
再次重申,本篇文章对单链表的操作都是在建立头结点的基础上,就像一个火车头和后面多节车厢一样,无论后面车厢如何添加、删除,我们的火车头永远是“固定”的。
#include <stdio.h>
#include <assert.h>
#include <malloc.h>
#define ElemType int
typedef struct LNode
{
ElemType data;
struct LNode *next;
}LNode;
typedef LNode* LinkList;
//创建一个头结点
void InitList(LinkList *head)
{
*head = (LNode *)malloc(sizeof(LNode));
assert(*head != NULL);
(*head)->next = NULL;
}
//头插法建立单链表
void createListH(LinkList *head)
{
for(int i=1;i<=10;i++)
{
LNode *s = (LNode *)malloc(sizeof(LNode)); //s为新创建结点
assert(s!=NULL);
s->data = i;
s->next = (*head)->next;
(*head)->next = s;
}
}
//尾插法建立单链表
void createListrR(LinkList *head)
{
LNode *p = *head;
for(int i=1;i<=10;i++)
{
LNode *s = (LNode *)malloc(sizeof(LNode)); //s为新创建结点
assert(s!=NULL);
s->data = i;
s->next = p->next;
p->next = s;
p = s; //指向最后一个结点地址
}
}
//单链表中数据节点的个数
int listLength(LinkList *head)
{
LNode *p = *head;
int count=0;
while(p->next != NULL)
{
p = p->next;
count ++;
}
return count;
}
//链表是否为空
bool is_empty(LinkList *head)
{
return ((*head)->next == NULL);
}
//升序排序
void sort(LinkList *head)
{
LNode *p,*q;
ElemType temp;
for (p =(*head)->next; p != NULL; p = p->next)
{
for (q = p->next; q != NULL; q = q->next)
{
if (p->data > q->data)
{
temp = q->data;
q->data = p->data;
p->data = temp;
}
}
}
}
//特定下标处插入值
bool insert(LinkList *head, int pos, ElemType val)
{
int n = 0;
LNode *p = *head;
while (n < pos && p != NULL)
{
n++;
p = p->next;
}
if (n != pos || NULL == p)
{
return false;
}
LNode *pNew = (LNode *)malloc(sizeof(LNode));
if (NULL == pNew)
{
printf("Error in dynamic allocating memory!");
}
pNew->data = val;
pNew->next = p->next;
p->next = pNew;
return true;
}
// 删除某个节点
bool listDelete(LinkList *head, int pos)
{
int n = 0;
LNode *p = *head;
while (n < pos && p != NULL)
{
n++;
p = p->next;
}
if (n != pos || NULL == p)
{
return false;
}
//判断位置合法
if(p->next == NULL)
{
return false;
}
LNode *q = p->next;
p->next = p->next->next;
free(q);
return true;
}
LNode* find(LinkList *head, ElemType key)
{
LNode *p = *head;
//为NULL或找到p
while (p != NULL && p->data != key) { p= p->next; }
return p;
}
//1.判断非空 2.删除头结点之后第一个结点
void pop_front(LinkList *head)
{
if((*head)->next == NULL)
{
printf("error: seqList is empty\n");
return;
}
LNode *q = (*head)->next;
(*head)->next = q->next;
free(q);
}
//1.判断非空 2.删除倒数第一个结点
void pop_back(LinkList *head)
{
if((*head)->next == NULL)
{
printf("error: seqList is empty\n");
return;
}
LNode *p = *head;
LNode *prev = NULL;//存p结点之前的结点
while (p->next != NULL)
{
prev = p;
p = p->next;
}
//prev为倒数第二个结点
//p为倒数第一个结点
prev->next = p->next;
free(p);
}
//仅显示,不修改
void showList(LinkList head)
{
LNode *p = head->next;
while(p != NULL)
{
printf("%d---->",p->data);
p = p->next;
}
printf("Nul\n");
}
int main()
{
LinkList mylist;
InitList(&mylist);
printf("create node in the head:\n");
createListH(&mylist);
//尾插
//createListrR(&mylist);
//打印显示
showList(mylist);
//插入值
insert(&mylist,2,88);
printf("insert 88 at pos 2:\n");
//打印显示
showList(mylist);
//查找
if(!find(&mylist,3))
printf("value 3 not found\n");
//长度
printf("length:%d\n", listLength(&mylist));
//头删
pop_front(&mylist);
//打印显示
printf("delete head element:\n");
showList(mylist);
//尾删
pop_back(&mylist);
//打印显示
printf("delete tail element:\n");
showList(mylist);
//删除下标为2
if(listDelete(&mylist,2)) printf("delete success\n");
else printf("delete failed\n");
//打印显示
printf("delete value at pos 10:\n");
showList(mylist);
//升序排序
sort(&mylist);
printf("sort ascend:\n");
//打印显示
showList(mylist);
//判断是否为空
if(is_empty(&mylist)) printf("linklist is empty\n");
else printf("linklist is not empty\n");
return 0;
}
四、验证
在main函数中,我们首先建立了头结点,然后使用前插法建立单链表。之后就是利用我们所实现的函数进行一系列的验证: