单循环链表

对于单链表而言,如果每次在遍历到单链表中间处时需要遍历整个链表,此时只能往后遍历,前方的指针便会丢失。如图1所示,此时若链表遍历到a2处依旧可以通过尾结点循环到a1处,这是单链表所不能解决的。

图1

带头结点头指针的单循环链表

关于单链表的存取,有时候我们在单链表的第一个结点(有效元素)之前附设一个结点,称之为头结点;指向头结点的指针,称之为头指针如图2;

图2

对于单链表来说,不同在于判空,以及尾结点的next为头结点。

/* 初始化链式线性表 */

Status InitList(LinkList* L)

{

*L = (LinkList)malloc(sizeof(Node)); /* 产生头结点,并使头指针L指向此头结点 */

if (!(*L)) /* 存储分配失败 */

return ERROR;

(*L)->next = *L; /* 单循环链表 */

return OK;

}

/* 初始条件:链式线性表L已存在。操作结果:将L重置为空表 */

Status ClearList(LinkList* L)

{

LinkList p, q;

p = (*L)->next; /* p指向第一个结点 */

while (p != (*L)) /* 没到表尾 */

{

q = p->next;

free(p);

p = q;

}

(*L)->next = *L; /* 头结点指针域为空 */

return OK;

}

插入和删除可由图三、图四看出差距不大,可参照单链表的编写改基本操作。

图3

图4

带头结点尾指针的单循环链表

对于带头结点尾指针的单循环链表而言,其不仅判空和初始化不同,就连插入操作和删除操作也不同。如图5.

图5

对于插入和删除而言,由于只知道尾指针,于是对于查找头结点和尾结点都可以O(1)进行。

L->rear->next->next //首节点。

L->rear //尾指针指向的尾结点。

学习心得

1.在头结点、头指针、尾结点和尾指针的设置时一定要弄清楚。malloc(sizeof(Node))创建的是一个结点,而(LinkList)malloc(sizeof(Node))是创建的是一个结构体指针。两者的返回值都是指向内存首地址的指针,但(LinkList)进行了强制的类型转换。因为malloc()函数的返回值类型是 void *,void 并不是说没有返回值或者返回空指针,而是返回的指针类型未知。所以在使用 malloc() 时通常需要进行强制类型转换,将 void 指针转换成我们希望的类型。

2.初始化工作在头结点头指针和头结点尾指针是不同的。

头结点头指针的定义:

typedef struct Node

{

ElemType data;

struct Node* next;/*定义下一个Node的指针,结点内依旧包含data和next指针*/

}Node;

typedef struct Node* LinkList; /* 定义LinkList */

头结点尾指针的定义:

typedef struct Node

{

ElemType data;

struct Node* next;/*定义下一个Node的指针,结点内依旧包含data和next指针*/

}Node;

typedef struct {

Node* rear;

}*LinkListLast;/* 定义LinkList */

我们可以看到两者在定义时是不同,头结点头指针可以在定义结点的时候直接定义结点首地址的指针,而头结点尾指针多了对于尾指针指向的尾结点的定义如图6。

图6

所以在头结点尾指针的初始化时候需要分配两次内存。

/* 初始化链式线性表 */

/* LinkListLast*表示一个结点,使用*L来接受它的返回的首地址

LinkListLast表示链表

*/

Status InitList(LinkListLast* L)

{

(*L) = (LinkListLast)malloc(sizeof(Node)); /*定义链表*/

(*L)->rear = (Node *)malloc(sizeof(Node)); /* 产生头结点,并使尾指针指向此头结点 */

if (!(*L)->rear) /* 存储分配失败 */

return ERROR;

(*L)->rear->next= (*L)->rear; /* 单循环链表 */

return OK;

}

3.在特定位置之前插入和删除某个特定的结点不同,由于前驱结点的指针无法知道所以容易free(p)后找不到前驱结点。此时我们可以创建一个q结点,q为p前驱,qp一起移动。

Status ListInsert(LinkListLast* L, int i, ElemType e,int k)

{

int j=1;

Node *p,*s;

p = (*L)->rear->next->next;

if (i == 1) {

ListInsertHead(*L, e);

return OK;

}

if (i == k) {

ListInsertTail(*L, e);

return OK;

}

while (p!=(*L)->rear && j < i-1) /* 寻找第i个结点 */

{

p = p->next;

++j;

}

if (!p||j > i) {

return ERROR;

}

s = malloc(sizeof(Node)); /* 生成新结点(C语言标准函数) */

s->data = e;

s->next = p->next;

p->next = s;

return OK;

}

Status ListDelete(LinkListLast* L, int i, ElemType* e)

{

int j;

Node *p,*q;

j = 1;

p = (*L)->rear->next->next;

q = malloc(sizeof(Node));

while (p != (*L)->rear->next && j < i) /* 遍历寻找第i个元素 */

{

q = p;

p = p->next;

++j;

}

if (p == (*L)->rear->next || j > i) {

return ERROR; /* 链表为空或第i个元素不存在 */

}

if (p == (*L)->rear) {

q->next = p->next;

*e = p->data;

(*L)->rear = q;

free(p);

return OK;

}

q->next = p->next;

*e = p->data;

free(p);

return OK;

}

4.对于头结点尾指针的链表来说,在首结点之前,尾结点之后,查找首尾结点时间复杂度都为O(1)。但需注意若改变在尾结点,那么讨论是否应该改变尾指针。

/* 初始条件:链式线性表L已存在,1≤i≤ListLength(L), */

/* 操作结果:在L表尾插入新的数据元素e,L的长度加1 */

Status ListInsertTail(LinkListLast* L, ElemType e)

{

Node * s;

if (!(*L)) {

return ERROR;

}

s = malloc(sizeof(Node)); /* 生成新结点(C语言标准函数) */

s->data = e;

s->next = (*L)->rear->next; /* 将p的后继结点赋值给s的后继 */

(*L)->rear->next = s; /* 将s赋值给p的后继 */

(*L)->rear = s;

return OK;

}

完整代码(带头结点尾指针的单循环链表)

#include "stdio.h"

#include "string.h"

#include "stdlib.h"

#include "math.h"

#include "time.h"

#define OK 1

#define ERROR 0

#define TRUE 1

#define FALSE 0

#define MAXSIZE 20 /* 存储空间初始分配量 */

typedef int Status;/* Status是函数的类型,其值是函数结果状态代码,如OK等 */

typedef int ElemType;/* ElemType类型根据实际情况而定,这里假设为int */

Status visit(ElemType c)

{

printf("%d ", c);

return OK;

}

typedef struct Node

{

ElemType data;

struct Node* next;/*定义下一个Node的指针,结点内依旧包含data和next指针*/

}Node;

typedef struct {

Node* rear;

}*LinkListLast;/* 定义LinkList */

/* 初始化链式线性表 */

/* LinkListLast*表示一个结点,使用*L来接受它的返回的首地址

LinkListLast表示链表

*/

Status InitList(LinkListLast* L)

{

(*L) = (LinkListLast)malloc(sizeof(Node));

(*L)->rear = (Node *)malloc(sizeof(Node)); /* 产生头结点,并使尾指针指向此头结点 */

if (!(*L)->rear) /* 存储分配失败 */

return ERROR;

(*L)->rear->next= (*L)->rear; /* 单循环链表 */

return OK;

}

/* 初始条件:链式线性表L已存在。操作结果:若L为空表,则返回TRUE,否则返回FALSE */

Status ListEmpty(LinkListLast L)

{

if (L->rear->next != L->rear)

return FALSE;

else

return TRUE;

}

/* 初始条件:链式线性表L已存在。操作结果:将L重置为空表 */

Status ClearList(LinkListLast* L)

{

Node* p, * q;

LinkListLast Head;

p = (*L)->rear->next->next; /* p指向第一个结点 */

Head = (*L)->rear->next;

while (p != Head) /* p是结构体指针,没到表尾 */

{

q = p->next;

free(p);

p = q;

}

(*L)->rear->next= (*L)->rear; /*尾指针指向头结点*/

return OK;

}

/* 初始条件:链式线性表L已存在。操作结果:返回L中数据元素个数 */

int ListLength(LinkListLast L)

{

int i = 0;

Node* p; /* p指向第一个结点 */

p = L->rear->next->next;

while (p != L->rear->next)

{

i++;

p = p->next;

}

return i;

}

/* 初始条件:链式线性表L已存在,1≤i≤ListLength(L) */

/* 操作结果:用e返回L中第i个数据元素的值 */

Status GetElem(LinkListLast L, int i, ElemType* e)

{

int j;

Node *p; /* 声明一结点p */

p = L->rear->next->next; /* 让p指向链表L的第一个结点 */

j = 1; /* j为计数器 */

while (p != L->rear->next && j < i) /* p不为空或者计数器j还没有等于i时,循环继续 */

{

p = p->next; /* 让p指向下一个结点 */

++j;

}

if ( !p||j > i)

return ERROR; /* 第i个元素不存在 */

*e = p->data; /* 取第i个元素的数据 */

return OK;

}

/* 初始条件:链式线性表L已存在 */

/* 操作结果:返回L中第1个与e满足关系的数据元素的位序。 */

/* 若这样的数据元素不存在,则返回值为0 */

int LocateElem(LinkListLast L, ElemType e)

{

int i = 0;

Node* p = L->rear->next->next;

while (p!=L->rear->next)

{

i++;

if (p->data == e) /* 找到这样的数据元素 */

return i;

p = p->next;

}

return 0;

}

/* 初始条件:链式线性表L已存在,1≤i≤ListLength(L), */

/* 操作结果:在L中第i个位置之前插入新的数据元素e,L的长度加1 */

Status ListInsert(LinkListLast* L, int i, ElemType e,int k)

{

int j=1;

Node *p,*s;

p = (*L)->rear->next->next;

if (i == 1) {

ListInsertHead(*L, e);

return OK;

}

if (i == k) {

ListInsertTail(*L, e);

return OK;

}

while (p!=(*L)->rear && j < i-1) /* 寻找第i个结点 */

{

p = p->next;

++j;

}

if (!p||j > i) {

return ERROR;

}

s = malloc(sizeof(Node)); /* 生成新结点(C语言标准函数) */

s->data = e;

s->next = p->next;

p->next = s;

return OK;

}

/* 初始条件:链式线性表L已存在,1≤i≤ListLength(L), */

/* 操作结果:在L中表头插入新的数据元素e,L的长度加1 */

Status ListInsertHead(LinkListLast* L, ElemType e)

{

Node * s;

if (!(*L)) {

return ERROR;

}

if ((*L)->rear == (*L)->rear->next) {

s = malloc(sizeof(Node)); /* 生成新结点(C语言标准函数) */

s->data = e;

s->next = (*L)->rear->next; /* 将p的后继结点赋值给s的后继 */

(*L)->rear->next = s; /* 将s赋值给p的后继 */

(*L)->rear = s;

return 0;

}

s = malloc(sizeof(Node)); /* 生成新结点(C语言标准函数) */

s->data = e;

s->next = (*L)->rear->next->next; /* 将p的后继结点赋值给s的后继 */

(*L)->rear->next->next =s ; /* 将s赋值给p的后继 */

return OK;

}

/* 初始条件:链式线性表L已存在,1≤i≤ListLength(L), */

/* 操作结果:在L表尾插入新的数据元素e,L的长度加1 */

Status ListInsertTail(LinkListLast* L, ElemType e)

{

Node * s;

if (!(*L)) {

return ERROR;

}

s = malloc(sizeof(Node)); /* 生成新结点(C语言标准函数) */

s->data = e;

s->next = (*L)->rear->next; /* 将p的后继结点赋值给s的后继 */

(*L)->rear->next = s; /* 将s赋值给p的后继 */

(*L)->rear = s;

return OK;

}

/* 初始条件:链式线性表L已存在,1≤i≤ListLength(L) */

/* 操作结果:删除L的第i个数据元素,并用e返回其值,L的长度减1 */

Status ListDelete(LinkListLast* L, int i, ElemType* e)

{

int j;

Node *p,*q;

j = 1;

p = (*L)->rear->next->next;

q = malloc(sizeof(Node));

while (p != (*L)->rear->next && j < i) /* 遍历寻找第i个元素 */

{

q = p;

p = p->next;

++j;

}

if (p == (*L)->rear->next || j > i) {

return ERROR; /* 链表为空或第i个元素不存在 */

}

if (p == (*L)->rear) {

q->next = p->next;

*e = p->data;

(*L)->rear = q;

free(p);

return OK;

}

q->next = p->next;

*e = p->data;

free(p);

return OK;

}

/* 初始条件:链式线性表L已存在 */

/* 操作结果:依次对L的每个数据元素输出 */

Status ListTraverse(LinkListLast L)

{

Node* p = L->rear->next->next;

while (p!=L->rear->next)

{

visit(p->data);

p = p->next;

}

printf("\n");

return OK;

}

int main()

{

LinkListLast L;

ElemType e;

Status i;

int j, k;

i = InitList(&L);

printf("初始化L后:ListLength(L)=%d\n", ListLength(L));

for (j = 1; j <= 10; j++)

ListInsertTail(&L, j);

printf("在L的表尾依次插入1~10后:L.data=");

ListTraverse(L);

for (j = 12; j <= 17; j++)

i = ListInsert(&L,3, j,ListLength(L));

printf("在L的第三个位置依次插入12~17后:L.data=");

ListTraverse(L);

printf("ListLength(L)=%d \n", ListLength(L));

i = ListEmpty(L);

printf("L是否空:i=%d(1:是 0:否)\n", i);

i = ClearList(&L);

printf("清空L后:ListLength(L)=%d\n", ListLength(L));

i = ListEmpty(L);

printf("L是否空:i=%d(1:是 0:否)\n", i);

for (j = 1; j <= 10; j++)

ListInsertHead(&L, j);

printf("在L的表头依次插入1~10后:L.data=");

ListTraverse(L);

printf("ListLength(L)=%d \n", ListLength(L));

ListInsertHead(&L, 0);

printf("在L的表头插入0后:L.data=");

ListTraverse(L);

printf("ListLength(L)=%d \n", ListLength(L));

GetElem(L, 5, &e);

printf("第5个元素的值为:%d\n", e);

for (j = 3; j <= 4; j++)

{

k = LocateElem(L, j);

if (k)

printf("第%d个元素的值为%d\n", k, j);

else

printf("没有值为%d的元素\n", j);

}

k = ListLength(L); /* k为表长 */

for (j = k + 1; j >= k; j--)

{

i = ListDelete(&L, j, &e); /* 删除第j个数据 */

if (i == ERROR)

printf("删除第%d个数据失败\n", j);

else

printf("删除第%d个的元素值为:%d\n", j, e);

}

printf("依次输出L的元素:");

ListTraverse(L);

j = 5;

ListDelete(&L, j, &e); /* 删除第5个数据 */

printf("删除第%d个的元素值为:%d\n", j, e);

printf("依次输出L的元素:");

ListTraverse(L);

i = ClearList(&L);

printf("\n删除L后:ListLength(L)=%d\n", ListLength(L));

return 0;

}

怎么样让指针指向尾结点C语言,带头结点头指针与带头结点尾指针的学习相关推荐

  1. c语言循环链表中设立尾链表,C语言实现双向非循环链表(带头结点尾结点)的节点插入...

    对于双向链表,个人推荐使用带头结点尾结点的方式来处理会比较方便.我在<C语言实现双向非循环链表(不带头结点)的节点插入>中详细实现了在不带头结点的情况下的插入.这次我们将会来在使用头结点尾 ...

  2. c语言指针指向怎么指,C语言-基础教程-指向指针的指针

    一个指针变量可以指向整型变量.实型变量.字符类型变量,当然也可以指向指针类型变量.当这种指针变量用于指向指针类型变量时,我们称之为指向指针的指针变量,这话可能会感到有些绕口,但你想到一个指针变量的地址 ...

  3. c语言指针指向字符串单个,C语言 有没有可能调用一个指向字符串的函数指针?...

    我是个在学C的萌新,一天突发奇想,指令和数据只是对人来说才有意义, 一段二进制串对CPU来说既可是数据,也可是指令,IP指向哪里就当作指令执行.那这样的话是不是意味着可以在C中执行字符串呢? 可,在探 ...

  4. 【C 语言】二级指针作为输入 ( 自定义二级指针内存 | 二级指针排序 | 通过 交换指针指向的内存数据 方式进行排序 )

    文章目录 一.二维指针 排序 ( 通过 交换指针指向的内存数据 方式进行排序 ) 二.完整代码示例 一.二维指针 排序 ( 通过 交换指针指向的内存数据 方式进行排序 ) 在上一篇博客 [C 语言]二 ...

  5. 【精华文】C语言结构体特殊情况分析:结构体指针 / 基本数据类型指针,指向其他结构体

    参考链接:Structure pointer pointing to different structure instance 注:可以查看此篇的问题和唯一的回复,那是相对正确的,不要看comment ...

  6. c语言指针的地址存放,c语言 - *指针 和 地址

    最近在研究oc的底层,全是c/c++的代码,虽然以前学过也写过,其实不怎么用都忘得差不多了. 首先我们来了解一下 * 和 &这两个符号 通俗点儿理解其实&地址就是就是一个存放地址的变量 ...

  7. 『C语言从入门到进阶』第 ⑥ 期 - 初识指针

    学习导航:> 1.指针是什么? 2.指针和指针类型 2.1指针+-整数 2.2指针的解引用 3.野指针 3.1野指针成因 3.2如何规避野指针 4.指针运算 4.1指针+-整数 4.2指针-指针 ...

  8. C语言调用 free 函数释放内存后指针指向及内存中的值是否改变的问题

    文章目录 1. 前言 2. 正文 2.1. "分配" 与 "释放" 2.2. 运行测试 2.2.1. VSCode 下使用 gcc 编译 2.2.2. VS20 ...

  9. 让指针指向初始位置c语言,初始C语言中的指针(翁凯男神MOOC)

    运算符  & ●scanf("%d",&i); ●获得变量的地址,它的操作数必须是变量 ● int i; printf("%x",&i) ...

最新文章

  1. logback配置文件
  2. 释疑の资源短缺DATASET_CANT_CLOSE
  3. jdk注解_我们正在下注:这个注解很快就会出现在JDK中
  4. Linux开机启动过程(15):start_kernel()->rcu_init()初始化
  5. oracle视图查询机制,物化视图及日志内部机制的一点研究
  6. CSS自定义动画@keyframes的使用
  7. 第015讲 仿sohu首页面布局
  8. 基于matlab的64QAM通信系统的仿真
  9. postman设置成中文
  10. DBMS_AUDIT_MGMT.FLUSH_UNIFIED_AUDIT_TRAIL过程
  11. 用sk-learn实现新闻的分类预测(完整代码)
  12. 三层交换机配置实现不同网络互通
  13. 【小Game】C++ - EGE - 躲避球小游戏
  14. Fatal error: The slave I/O thread stops because master and slave have equal MySQL server UUIDs
  15. js-url转换blob以及blob与base64的相互转换
  16. LVS负载均衡—基于Keepalived做高可用
  17. 你管这叫操作系统源码(五)
  18. 名正则言顺�谈服装品牌名称(二)
  19. kali安装keylogger_小白日记48:kali渗透测试之Web渗透-XSS(二)-漏洞利用-键盘记录器,xsser...
  20. 新时达服务器说明书_新时达SM-01-F5021使用说明书V6.0

热门文章

  1. 关于CORS(跨源资源共享)实践
  2. 拯救懒癌晚期,拖延症晚期---番茄工作法
  3. 微信小程序如何实现上拉加载更多数据?
  4. C:字符串相关函数,gets、fgets()、puts()、fputs()
  5. 【编译原理】写出下列文法对应定义的是什么语言?
  6. 可持续微电网能源管理
  7. php class 构造_PHP 类与构造函数
  8. UNIX SOCKET简介
  9. Zepeto难逃昙花一现的命运 但虚拟形象的未来不得不引人深思
  10. 柔性屏是怎么被手机厂抢掉风头的?