链表 教程

链表简介、操作及学习关键点

一、链表简介

1.什么是链表?

链表是一种线性数据结构,它由一系列节点组成,每个节点包含两部分:存储的数据和指向下一个节点的指针。与数组不同,链表中的元素不是连续存储在内存中,而是通过指针链接在一起。

2.

链表结构介绍

(1)数据域:存储数据元素信息的域。

(2)指针域:存储直接后继位置的域。

(3)结点:数据域和指针域组成的存储映像。

(4)头指针:链表中的第一个结点的存储位置。

之后的每一个结点,都位于上一个结点其后继指针所指向的位置, 最后一个结点的指针为“空”,用NULL符号表示。

(5)头结点:单链表的第一个结点。

①头结点的数据域可以不存储任何信息,也可以存储线性表长度等附加信息,头结点的指针域存储指向第一个结点的指针。

②头结点不是必须的,它的设立是为了操作的统一与方便,有了头结点,对在第一元素结点的前插与删除操作,与其他结点的操作是统一的。

(6)单链表:每个结点中只包含一个指针域的链表。

3.链表的特点

1动态大小:链表可以在运行时根据需要添加或删除节点。

2高效的插入和删除:相比于数组,在链表中插入和删除元素通常更高效。

3随机访问效率低:访问链表中的某个元素,必须从头开始遍历直到目标位置。

二、单向链表的基本操作

1.EasyX初始化函数

(1)定义图形窗口相关参数,并对需要外部调用的函数进行声明。

//displayList.h

#pragma once

//EasyX 窗口尺寸

#define EASYX_WIDTH 1280

#define EASYX_HEIGHT 720

//EasyX汉字字体高度

#define TEXT_HEIGHT 20

#define MAX_DEBUG_CHAR 512

//函数声明

void init_display_window(void);

void end_display(void);

(2)实现初始化显示窗口函数,显示个测试字符,然后结束显示。

//displayList.cpp

//初始化EasyX显示窗口

void init_display_window(void)

{

initgraph(EASYX_WIDTH,EASYX_HEIGHT); //创建图形窗口

setbkcolor(WHITE);

cleardevice(); //使用背景色擦除整个屏幕

settextcolor(BLACK); //设置字体颜色

settextstyle(TEXT_HEIGHT,0,_T("微软雅黑")); //设置字体样式

setbkmode(TRANSPARENT); //设置字体为透明背景

outtextxy(10 , 20 , _T("ABCDEF"));

}

//结束显示

void end_display(void)

{

_getch(); //程序稍做暂停

closegraph();

}

  1. 在main函数中调用初始化与结束显示的函数。

//ListDemo.cpp

int main()

{

init_display_window();

end_display();

return 0;

}

测试结果:

                 

3.创建链表

创建链表,需要多个节点并将它们连接起来。

// 在链表中插入一个新的卡片结点

void insert_card(CardList *list, NodeCard* newCard) {NodeCard *current = (NodeCard*)*list; // 将当前指针设置为链表的头结点

while(current->next != NULL)

{

// 遍历链表直到最后一个结点

current = current->next;

// 移动到下一个结点

}

current->next = newCard; // 将最后一个结点的next指针指向新插入的卡片结点

}

// 显示链表中结点间的箭头,表示指针的指向关系

void display_arrows(CardList list)

{

    CardList p = list; // 从头结点开始遍历

    NodeCard *tmp;

    while(p)

    { // 遍历链表直到NULL

        if(p->next != NULL && p->pointer == p->next->address)

        { // 如果当前结点的next不为空,并且当前结点的指针域等于下一个结点的地址域

            int x1 = p->right; // 起点的x坐标为当前结点的右边界

            int y1 = (p->bottom + p->top)/2; // 起点的y坐标为当前结点的垂直中心

            int x2 = p->next->left; // 终点的x坐标为下一个结点的左边界

            int y2 = (p->next->bottom + p->next->top)/2; // 终点的y坐标为下一个结点的垂直中心

            draw_arrow(x1, y1, x2, y2); // 绘制箭头

        }

        p = p->next; // 移动到下一个结点

    }

}

// 显示链表的图形化表示,并绘制结点间的箭头

void display_list(LinkList L)

{

    LinkList p = L; // 从头结点开始遍历

    int tmpx = 10; // 初始化结点卡片的横坐标

    int tmpy = 10; // 初始化结点卡片的纵坐标

NodeCard *tmp; // 临时变量,用于存储结点卡片的指针

while(p)

{

// 遍历链表直到NULL

    tmp = display_card(tmpx, tmpy, 0, p); // 显示当前结点的卡片,并获取卡片的指针

    insert_card(&CL, tmp); // 将当前结点的卡片插入到卡片链表中

   tmpx = tmp->right + 50; // 增加横坐标,为下一个结点卡片预留空间

   if(tmpx > EASYX_WIDTH - (tmp->right - tmp->left))

   {

        tmpx = 10; // 重置横坐标到初始值

        tmpy += tmp->bottom + 20; // 增加纵坐标,换行显示

    }

    p = p->next; // 移动到下一个结点

}

 display_arrows(CL); // 绘制卡片链表中结点间的箭头

 clear_card_list(&CL); // 清空卡片链表,释放内存

}

这段代码主要实现了三个功能:

  1. insert_card:在卡片链表的末尾插入一个新的卡片结点。

  1. display_arrows:遍历卡片链表,并在相邻结点间绘制箭头,表示链表中结点的连接关系。

3.display_list:遍历链表,显示每个结点的卡片,并在卡片链表中插入对应的卡片结点,最后绘制所有结点间的连接箭头,并清空卡片链表。

运行结果:

4.插入节点

插入节点:(1)头插法(2)尾插法(3)中间插入

  1. 头插法

/*  随机产生n个元素的值,建立带表头结点的单链线性表L(头插法) */

void CreateListHead(LinkList *L, int n)

{

    LinkList p;

    int i;

    srand(time(0));                         /* 初始化随机数种子 */

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

    (*L)->next = NULL;                      /*  先建立一个带头结点的单链表 */

    for (i=0; i<n; i++)

    {

        p = (LinkList)malloc(sizeof(Node)); /*  生成新结点 */

        p->data = rand()%100+1;             /*  随机生成100以内的数字 */

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

        (*L)->next = p;                        /*  插入到表头 */

    }

}

  1. 尾插法

/*  随机产生n个元素的值,建立带表头结点的单链线性表L(尾插法) */

void CreateListTail(LinkList *L, int n)

{

    LinkList p,r;

    int i;

    srand(time(0));                      /* 初始化随机数种子 */

    *L = (LinkList)malloc(sizeof(Node)); /* L为整个线性表 */

    r=*L;                                /* r为指向尾部的结点 */

    for (i=0; i<n; i++)

    {

        p = (Node *)malloc(sizeof(Node)); /*  生成新结点 */

        p->data = rand()%100+1;           /*  随机生成100以内的数字 */

        r->next=p;                        /* 将表尾终端结点的指针指向新结点 */

        r = p;                            /* 将当前的新结点定义为表尾终端结点 */

    }

    r->next = NULL;                       /* 表示当前链表结束 */

}

5.插入和删除

  1. 插入

//动画演示新结点插入的过程,参数为链表,位置,新结点数据

Status display_list_insert(LinkList *L,int i,ElemType e)

{

   

    int cnt;

    LinkList p,s;

    p = *L;  

    cnt = 1;

    while (p && cnt < i)     /* 寻找第i个结点 */

    {

        p = p->next;

        ++cnt;

    }

    if (!p || cnt > i)

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

    //擦除屏幕靠下区域

    setfillcolor(WHITE);

    solidrectangle(0,200,EASYX_WIDTH,EASYX_HEIGHT);

    debug_info(_T("执行插入命令,在%d位置,插入节点%d。。。"),i,e);

    int sleep_time=1000;

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

    display_text(800,200,_T("结点申请内存:s=(LinkList)malloc(sizeof(Node));"));

    display_card(10,200,0,p);//显示前一个结点

    display_card(400,200,1,s);//显示新建的结点

    //_getch();

    Sleep(sleep_time);

    s->data = e;  

    display_text(800,300,_T("结点数据域赋值:s->data = e;"));

    display_card(10,300,0,p);//显示前一个结点

    display_card(400,300,1,s);//显示新建的结点

    _getch();

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

    display_text(800,400,_T("将p的后继结点赋值给s的后继:s->data =  p->next;"));

    display_card(10,400,0,p);//显示前一个结点

    display_card(400,400,1,s);//显示新建的结点

    _getch();

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

    display_text(800,500,_T("将s赋值给p的后继:p->next = s"));

    display_card(10,500,0,p);//显示前一个结点

    display_card(400,500,1,s);//显示新建的结点

    _getch();

    setfillcolor(WHITE);

    solidrectangle(0,0,EASYX_WIDTH,200);

    display_list(*L);

    debug_info(_T("结点%d已经插入完毕,按任意键结束\n"));

    _getch();

    return OK;

}

插入动画演示:(双击播放视频)

插入动画演示

  1. 删除

//列表中删除结点

Status display_List_delete(LinkList *L,int i,ElemType *e)

{

    int cnt;

    LinkList p,s;   /* 声明结点p与s(类型为指向结点的指针) */

    p = *L;

    cnt = 1;

    while (p->next && cnt < i)    /* 遍历寻找第i个元素 */

    {

        p = p->next;

        ++cnt;

    }

    if (!(p->next) || cnt > i)

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

    setfillcolor(WHITE);

    solidrectangle(0,200,EASYX_WIDTH,EASYX_HEIGHT);

    debug_info(_T("执行删除位置,删掉第%d个结点\n"),i);

    int sleep_time=1000;

    _getch();

    s = p->next;

    display_text(800,200,_T("找到待删除结点s,p是s的前一个结点, s = p->next;"));

    display_text(80,170,_T("结点p"));

    display_card(10,200,0,p);

    display_text(470,170,_T("结点s"));

    display_card(400,200,1,s);

    Sleep(sleep_time);

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

    display_text(800,300,_T("将s的后继赋值给p的后继,p->next = s->next; "));

    display_text(80,270,_T("结点p"));

    display_card(10,300,0,p);

    display_text(470,270,_T("结点s"));

    display_card(400,300,1,s);

    Sleep(sleep_time);

    *e = s->data;               /* 将s结点中的数据给e */

    free(s);                    /* 让系统回收此结点,释放内存 */

    if(p->next)

    {

    display_text(800,400,_T("将s释放,free(s);  "));

    display_text(80,370,_T("结点p"));

    display_card(10,400,0,p);

    display_text(470,370,_T("结点p的后继结点"));

    display_card(400,400,1,p->next);

    Sleep(sleep_time);

    }

    else

    {

    display_text(470,370,_T("不存在 结点p的后置结点"));

    }

    setfillcolor(WHITE);

    solidrectangle(0,0,EASYX_WIDTH,170);

    display_list(*L);

    debug_info(_T("结点%d已经删除完毕,按任意键继续\n"),*e);

    _getch();

    return OK;

}

删除动画演示:(双击播放视频)

删除动画演示

三、学习链表的关键点

1.理解节点:每个节点不仅包含数据部分,还有一个或多个指向其他节点的链接(指针)。对于初学者来说,理解这一点至关重要。

2.熟悉指针:熟练掌握指针的概念及其操作是使用链表的基础。了解如何安全地操作指针可以避免许多常见的错误。

3.练习基本操作:创建、插入、删除节点等基础操作是学习链表的核心。通过实践这些操作,可以帮助加深对链表工作原理的理解。

4.考虑边界情况:在实现链表操作时,考虑到空列表、只有一个节点的列表等情况是非常重要的,因为这些特殊情况往往容易引发错误。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值