目录
前言
线性表链式存储结构的特点是用一组任意的存储单元来存放线性表中的元素,这组存储单元可以是连续的,也可以是不连续的,因此,链表中结点的逻辑顺序和物理顺序不一定相同。
单链表的结点包括俩个域:数据域和指针域,由于单链表中第一个结点没有前驱,通常会给单链表设置一个头指针H,习惯上用头指针代表单链表,同时,由于最后一个结点没有后继,所以他的指针域为空(NULL)
单链表的存储结构
#include <stdio.h>
#define TRUE 1
#define FALSE 0
typedef int DataType;
//单链表的存储结构
typedef struct LNode
{
DataType data;
struct LNode* next;
}LNode,*LinkList;
LNode *与Link List等价的,都是指向一个结点的指针,通常我们在代码中表示一个结点时使用LNode *,表达一个表的建立时使用LinkList,
合适的地方使用合适的名字,这样可以使得代码的可读性更强
带头结点的单链表的初始化
//带头结点的单链表的初始化
bool InitList(LinkList& L)
{
L = new LNode; //创建一个新结点
if (L == NULL)
return FALSE; //内存不足,分配失败
L->next = NULL; //头结点之后暂时没有结点
return TRUE;
}
带头结点判断单链表是否为空
//带头结点判断单链表是否为空
bool Empty(LinkList L)
{
if (L->next == NULL)
return TRUE;
else
return FALSE;
}
带头节点的单链表尾插法建立单链表
//带头节点的单链表尾插法建立单链表
LinkList List_Taillnsert(LinkList& L)
{
LNode* r, * s;
int x; //设DataType为整形
L = new LNode; //建立头节点
L->next = NULL; //初始化为空链表
r = L;//r为表尾指针,始终指向表尾
scanf("%d", &x);//输入结点的值
while (1)
{
if (x != 9999) //输入9999表示结束
{
s = new LNode;
s->data = x;
r->next = s;
r = s; //r指向性的表尾结点
scanf("%d", &x);
}
r->next = NULL;//表尾结点置空
if (x == 9999)
break;
}
return L;
}
带头节点的单链表头插法建立单链表
//带头节点的单链表头插法建立单链表
LinkList List_Headlnsert(LinkList L)
{
int x;//设DataType为整形
LNode* s;
L = new LNode;//建立头节点
L->next = NULL;//初始化为空链表
scanf("%d", &x);//输入结点的值
while (1)
{
if (x != 9999)//输入9999表示结束
{
s = new LNode;
s->data = x;
s->next = L->next;
L->next = s;
scanf("%d", &x);
}
if (x == 9999)
break;
}
return L;
}
带头结点在第i个位置插入元素e
//带头结点在第i个位置插入元素e
bool ListInsert(LinkList& L, int i, DataType e)
{
if (i < 1)
{
printf("插入的位置不合法!\n");
return FALSE;
}
LNode* p; //指针p指向当前扫描到的结点
p = L;//L指向头结点,头结点是第0个结点(不存数据)
int j = 0; //当前p指向的是第几个结点
while (p != NULL && j < i - 1) //循环找到第i-1个结点
{
p = p->next;
j++;
}
if (p == NULL) //i值不合法
{
printf("插入的位置不合法!\n");
return FALSE;
}
LNode* s = new LNode;
s->data = e;
s->next = p->next;
p->next = s; //将结点s连到p之后
return TRUE; //插入成功
}
后插操作,在p结点之后插入元素e
//后插操作,在p结点之后插入元素e
bool InsertNextNode(LNode* p, DataType e)
{
if (p == NULL)
{
printf("插入位置不合法!\n");
return FALSE;
}
LNode* s = new LNode;
if (s == NULL)
{
printf("内存分配失败!\n");
return FALSE;
}
s->data = e;
s->next = p->next;
p->next = s;
return TRUE;
}
前插操作,在p结点之前插入元素e
由于单链表只有一个方向,由前驱指向后继,所以在后插操作中我们可以很轻松的由前驱找到需要插入的后继位置而想要完成前插操作我们该怎么办呢,这里,我们采用一种非常巧妙的设计,完成即时不知道前驱是什么,也能完成前插操作
下面请看代码实现:
bool InsertPriorNode(LNode* p, DataType e)
{
if (p == NULL)
{
printf("插入的位置不合法!\n");
return FALSE;
}
LNode* s = new LNode;
if (s == NULL)
{
printf("内存分配失败!\n");
return FALSE;
}
s->next = p->next;
p->next = s; //新结点s连到p之后
s->data = p->data; //将p中元素复制到s中
p->data = e; //p中元素覆盖为e
return TRUE;
}
这里的实现非常巧妙,我们现在需要p后面新链接一个后继结点,然后将p结点的数据放入到他的后继中去,这样,p的后继就为原来的p结点了
原来的p结点成为了现在的p结点的前驱,我们在讲需要插入的元素放入原来的p结点即可实现前插操作
就像姜文的《让子弹飞》中的黄四郎为了防止麻匪张麻子的报复,在城里给自己找了个替身,
最后自己成为了替身,讲出的那句著名台词:“什么,我成替身了!”,与这里的p结点一样:“什么,我成我的后继了!”
带头结点删除单链表第i个结点
//带头结点删除单链表第i个结点
bool ListDelete(LinkList& L, int i, DataType& e)
{
if (i < 1)
{
printf("删除的位置不合法!\n");
return FALSE;
}
LNode* p; //指针p指向当前扫描到的结点
p = L; //L指向头结点,头结点是第0个结点(不存数据)
int j = 0;//当前p指向的是第几个结点
while (p != NULL && j < i - 1)//循环找到第i-1个结点
{
p = p->next;
j++;
}
if (p->next == NULL)
{
printf("删除的位置不合法!\n");
return FALSE;
}
LNode* q = p->next;//令q指向被删除的结点
e = q->data; //用e返回元素的值
p->next = q->next; //将*q结点从链中断开
delete q; //释放结点的存储空间
return TRUE;
}
删除指定结点元素
由于需要删除指定的结点,我们需要修改他的前驱的next域,而单链表中我们又没办法找到他的前驱结点
所以这里采用和上面一样的方法,不需要知道前驱就能删除指定结点元素
下面请看代码实现:
bool DeleteNode(LNode* p)
{
if(p==NULL)
{
printf("删除的位置不合法!\n");
return FALSE;
}
LNode* q = p->next; //令q指向*p的后继结点
p->data = q->data;//和后继结点交换数据域
p->next = q->next;//将*p结点从链中断开
delete q;//释放后继结点的存储空间
return TRUE;
}
注意:此代码有一个BUG,那就是当我们想要删除的位置为单链表的最后一个结点时,我们所创建的q即为NULL,所以就没有data域,程序会出现BUG,所以当我们想要删除单链表最后一个元素的时候,使用前面的那种传入头指针的方式删除
带头结点的单链表按位查找,返回第i个元素
//带头结点的单链表按位查找,返回第i个元素
LNode* GetElem(LinkList L, int i)
{
if (i < 0)
{
printf("查找的位置不合法!\n");
return FALSE;
}
int j = 0;
LNode* p;
p = L;
while (p != NULL && j < i)
{
p = p->next;
j++;
}
return p;
}
按值查找,找到数据域==e的结点
//按值查找,找到数据域==e的结点
int LocateElem(LinkList L, DataType e)
{
int i=1;
LNode* p = L->next;
//从第1个结点开始查找数据域为e的结点
while (p != NULL && p->data != e)
{
i++;
p = p->next;
}
if (p == NULL)
{
return 0;
}
else
return i;//找到后返回该结点指针,否则返回NULL
}
带头结点求单链表的长度
//带头结点求单链表的长度
int Length(LinkList L)
{
int len = 0;
LNode* p = L;
while (p != NULL)
{
len++;
p = p->next;
}
return len;
}
显示单链表的所有结点
//显示单链表的所有结点
void OutPut(LinkList L)
{
LNode* p;
p = L->next;
while (p != NULL)
{
printf("%d ", p->data);
p = p->next;
}
printf("\n");
}
菜单
//菜单
void menu(void)
{
printf("\n------------------------------------\n");
printf(" 欢迎使用单链表操作演示!\n");
printf(" 1、单链表初始化\n");
printf(" 2、创建单链表\n");
printf(" 3、获取单链表的长度\n");
printf(" 4、向单链表插入结点\n");
printf(" 5、按值查找结点\n");
printf(" 6、按下标查找结点\n");
printf(" 7、按下标删除结点\n");
printf(" 8、退出操作演示!\n");
printf("------------------------------------\n");
}
主函数
int main(void)
{
LinkList L=0;
LNode* p;
int n;
int e;
int i;
while (1)
{
menu();
printf("请选择想要进行的操作:");
scanf("%d", &n);
switch (n)
{
case 1: printf("单链表初始化已完成!\n"); InitList(L); break;
case 2:printf("请输入结点的值,输入9999结束建立\n"); List_Taillnsert(L); OutPut(L); break;
case 3:printf("单链表的长度为:%d", Length(L)); break;
case 4:printf("请输入需要插入的位置和数值:"); scanf("%d%d", &i, &e); ListInsert(L, i, e); OutPut(L); break;
case 5:printf("请输入需要查找结点的值:"); scanf("%d", &e);i= LocateElem(L, e);
if (i == 0) { printf("没有找到!\n"); }
else { printf("找到了,下表是:%d\n", i); }break;
case 6:printf("请输入需要查找的结点的下标:"); scanf("%d", &e);p= GetElem(L, e);
if (p == NULL) { printf("没有找到!\n"); }
else printf("找到了,值为:%d\n", p->data); break;
case 7:printf("请输入需要删除的结点下标:"); scanf("%d", &e); p = GetElem(L, e); DeleteNode(p); OutPut(L); break;
case 8:printf("已退出操作演示!\n"); break;
default:printf("输入不正确,请重新输入!\n");
}
if (n == 8)
break;
}
return 0;
}
效果展示:
感谢观看!码字不易,如果本篇文章对您有帮助,麻烦点赞支持一下!!!感谢^_^!!