C——单向链表的学习

有时在写代码、阅读代码过程中,会感到自己的C语言基础知识不是很牢,因此会导致效率降低。所以,适时地再回过头来温习一下有关的基础知识会增进自己的理解,帮助是很大的。
在此,我自己写了一个程序,以便学习理解数据结构里很基本也很重要的一个部分——链表。当然,链表也可以细分为单链表、双向链表等。这里我写的是单链表。不仅复习了一下数据结构,还复习了一下C语言基础、指针,一举多得。

下面把代码分为:链表创建、表的遍历、链表插入一个新节点以及数据、链表删除一个节点、链表的销毁。这几个基础的操作,最后还有一个表的逆序输出。

===========================================
链表是一种物理存储单元上非连续、非顺序的链式存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。可以结合数组来进行区别,数组是一种线性结构,数组在内存中是连续存放的。
简单区别整理如下:
1、数组静态分配内存(固定长度),链表动态分配内存(长度不固定)。
2、数组在内存中连续存储数据,链表不连续。
3、数组数据在栈中,链表数据在堆中。

以图的形式给出一个单链表:
这里写图片描述
可以看到:首先要有一个头结点head。图中A、B、C、D分别指代有4个数据。他们在内存中的地址分别为1200、1360、1520、1199。
head保存的地址是指向链表中第一个节点,第一个节点又指向第二个节点……直到最后一个节点,即遇到某个地方为NULL,链表结束。因此,通过指针间接来访问某个变量,知道了这个指针即知道了这个变量存放的地方,而这个指针在上一个节点有保存下来了。所以一个一个节点通过指针“链接”了起来,形成了单链表。

由此我们可以获得:每个节点(除头节点外),包含了数据成员(实际的数据),以及指向下一个结构体类型节点的指针(即保存的下一个节点的地址)。

一个简单的list结构体如下:

typedef struct LINK_NODE    //typedef一种数据类型定义一个新名字,回忆一下对比#define的区别
{
    int data;    //数据成员
    struct LINK_NODE *next;     //指向下一个节点指针
}LINK_NODE;    //结构体类型的变量

一、list创建:

/* 创建一张单链表 */
LINK_NODE *listCreate(int cnt)
{
    LINK_NODE *head;    //头节点
    LINK_NODE *ptr = NULL;    //链接节点的指针
    LINK_NODE *node = NULL;   
    int i;

    head = (LINK_NODE *)malloc(sizeof(LINK_NODE));    //head头节点分配内存空间。
    if(NULL == head)
    {
        printf("head 内存分配失败,链表初始化失败\n");
        exit(0);
    }
    head->next = NULL;
    ptr = head;    //ptr指向了head,ptr便是用来将各个节点链接的指针

    if(cnt <= 0)
    {
        printf("请输入大于0的整数.\n");
        exit(0);
    }

    for(i=0; i<cnt; i++)
    {
        node = (LINK_NODE *)malloc(sizeof(LINK_NODE));    //创建新节点,新节点分配内存。
        if(NULL == node)
        {
            printf("node 内存分配失败!\n");
            exit(0);
        }
        else
        {
            printf("第[%d]个数据是:", i+1);    //这里i+1是指在头节点后的那个节点,是1号节点。头节点是0号。
            scanf("%d", &node->data);    //输入数据
            ptr->next = node;    //将新产生的node的地址给保存到了ptr->next中,ptr就和数据节点node链接起来了
            ptr = node;    //再将node的地址给ptr,此时ptr就是保存了当前数据的地址
        }
    }
    node->next = NULL;    //最后一个数据节点指向NULL,结束单向链表。

    return head;    //返回头结点的地址
}

二、list遍历:

void displayList(LINK_NODE *head)
{
    LINK_NODE *ptr = head;

    if(NULL == ptr)
    {
        printf("链表为空!\n");
    }
    else
    {
        while(NULL != ptr)
        {
            printf("%6d", ptr->data);   //把数据读出来
            ptr = ptr->next;    //并不断把ptr后移(也就是把next中保存的每次的新节点地址取得)
        }    //直到为NULL时,遍历结束
        printf("\n");
    }
}

三、list插入:
在某个指定的节点后插入

void insert(LINK_NODE *head, int pos, int data)
{
    LINK_NODE *ptr = head;
    LINK_NODE *node = NULL;
    int i = 1;


    while(ptr && i<pos)    //遍历这个链表,通过传入的pos参数决定在哪一个节点后插入新节点
    {
        ptr = ptr->next;
        ++i;
    }

    node = (LINK_NODE *)malloc(sizeof(LINK_NODE));    //创建一个将要插入的新节点
    node->data = data;   //新节点的新数据
    node->next = ptr->next;  //将原来ptr->next保存的节点地址给到新节点node->next中
    ptr->next = node;  //将新节点地址给ptr->next。
    //这样就把原来的“旧”链给断了,新增了一条“新”链。自行画个图可能好理解一些
}

四、list删除

void nodeDelete(LINK_NODE *head, int pos)
{
    LINK_NODE *ptr = head;
    LINK_NODE *node = NULL;
    int i = 1;

    while(ptr && i<pos)   //依然先遍历,找到想删除节点的位置
    {
        ptr = ptr->next;
        ++i;
    }

    node = (LINK_NODE *)malloc(sizeof(LINK_NODE));
    node = ptr->next;
    ptr->next = node->next;   
    //可以看到刚好就是插入操作的逆操作,还是可以自行画图加以理解

    free(node);   //就是把某个节点free掉
}

五、list销毁

void listDestroy(LINK_NODE *head)
{
    LINK_NODE *ptr = NULL;

    while(head)   //把包括头节点在内的所有节点都free掉
    {
        ptr = head;
        head = head->next;
        printf("Node freed :[%d]\n", ptr->data);
        free(ptr);
    }
}

六、链表逆序输出

LINK_NODE *link_revers(LINK_NODE *head)
{
    LINK_NODE *next;
    LINK_NODE *prev = NULL;

    while(NULL != head)
    {
        next = head->next;
        head->next = prev;
        prev = head;
        head = next;
    }
    //循环终止即head为NULL
    return prev;
}

用图示来说明逆序:
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述

主函数:

int main(int argc, char **argv)
{
    LINK_NODE *plist = NULL;
    int pos_insert, data, cnt;
    int pos_del;

    printf("输入节点个数(不包括头结点):");
    scanf("%d", &cnt);
    plist = listCreate(cnt);
    printf("display this list :\n");
    displayList(plist);

    plist = link_revers(plist);
    printf("逆序输出结果是:\n");
    displayList(plist);

    printf("输入要插入的节点的位置和数据(pos data):");
    scanf("%d %d", &pos_insert ,&data);
    if(pos_insert > cnt)
    {
        printf("超出list的范围!\n");
        exit(0);
    }
    else
    {
        insert(plist, pos_insert, data);
        printf("在第[%d]号节点后插入完毕的list:", pos_insert);
    }
    displayList(plist);

    printf("输入你要删除的节点:");
    scanf("%d", &pos_del);
    if(pos_del > cnt)
    {
        printf("超出list的范围!\n");
        exit(0);
    }
    else
    {
        nodeDelete(plist, pos_del);
        printf("删除第[%d]号节点后的list:", pos_del);
    }
    displayList(plist); 

    listDestroy(plist);

    return 0;
}

测试结果:
头节点是没有数据的,因此为0.
下标是从0开始的 . 0,1,2,3,4,……
这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值