欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

C语言 - 链表的基本操作

程序员文章站 2023-01-11 22:35:09
1,为什么要用到链表 数组作为存放同类数据的集合,给我们在程序设计时带来很多的方便,增加了灵活性。但数组也同样存在一些弊病。如数组的大小在定义时要事先规定,不能在程序中进行调整,这样一来,在程序设计...

1,为什么要用到链表

数组作为存放同类数据的集合,给我们在程序设计时带来很多的方便,增加了灵活性。但数组也同样存在一些弊病。如数组的大小在定义时要事先规定,不能在程序中进行调整,这样一来,在程序设计中针对不同问题有时需要3 0个大小的数组,有时需要5 0个数组的大小,难于统一。我们只能够根据可能的最大需求来定义数组,常常会造成一定存储空间的浪费。

我们希望构造动态的数组,随时可以调整数组的大小,以满足不同问题的需要。链表就是我们需要的动态数组。它是在程序的执行过程中根据需要有数据存储就向要求申请存储空间,决不构成对存储区的浪费。

链表是一种复杂的数据结构,其数据之间的相互关系使链表分成三种:单链表、循环链表、双向链表,下面将逐一介绍。

2,单向链表

单链表有一个头节点head,指向链表在内存的首地址。链表中的每一个节点的数据类型为结构体类型,节点有两个成员:整型成员(实际需要保存的数据)和指向下一个结构体类型节点的指针即下一个节点的地址(事实上,此单链表是用于存放整型数据的动态数组)。链表按此结构对各节点的访问需从链表的头找起,后续节点的地址由当前节点给出。无论在表中访问那一个节点,都需要从链表的头开始,顺序向后查找。链表的尾节点由于无后续节点,其指针域为空,写作为null。

如图所示

上图还给出这样一层含义,链表中的各节点在内存的存储地址不是连续的,其各节点的地址是在需要时向系统申请分配的,系统根据内存的当前情况,既可以连续分配地址,也可以跳跃式分配地址。

3,单向链表程序的实现
(1),链表节点的数据结构定义

[cpp] view plain copy 在code上查看代码片派生到我的代码片

struct node
{
int num;
struct node *p;
} ;


在链表节点的定义中,除一个整型的成员外,成员p是指向与节点类型完全相同的指针。

在链表节点的数据结构中,非常特殊的一点就是结构体内的指针域的数据类型使用了未定义成功的数据类型。这是在c中唯一规定可以先使用后定义的数据结构。

(2),链表的创建、输出步骤
单链表的创建过程有以下几步:

1 ) 定义链表的数据结构;

2 ) 创建一个空表;

3 ) 利用malloc ( )函数向系统申请分配一个节点;

4 ) 将新节点的指针成员赋值为空。若是空表,将新节点连接到表头;若是非空表,将新

节点接到表尾;

5 ) 判断一下是否有后续节点要接入链表,若有转到3 ),否则结束;

单链表的输出过程有以下几步

1) 找到表头;

2) 若是非空表,输出节点的值成员,是空表则退出;

3 ) 跟踪链表的增长,即找到下一个节点的地址;

4) 转到2 ).

(3),程序代码例子:

创建一个存放正整数单链表,输入0或小于0的数,结束创建链表,并打印出链表中的值,程序如下:

//
//  main.cpp
//  list链表
//
//  created by 于磊 on 16/8/19.
//  copyright ? 2016年 于磊. all rights reserved.
//

# include 
//# include 
# include 

typedef struct node
{
    int data;
    struct node * pnext;
} * pnode, node;

pnode establish_list (void);
void traverse_list (pnode phead);
bool is_empty(pnode phead);
int length_list(pnode phead);
void sort_list(pnode phead);
void insert_list(pnode phead, int pos, int val);
int delete_list(pnode phead, int pos, int val);
void freeer(pnode phead);

int main(void)
{
    pnode phead;
    int len, i, j, val;
    phead = establish_list();
    traverse_list(phead);

    if(is_empty(phead))
        printf("链表为空\n");
    else
        printf("链表不空\n");

    len = length_list(phead);
    printf("链表的长度为: %d\n", len);

    sort_list(phead);
    traverse_list(phead);

    printf("请输入您要在第几个节点插入\n");
    scanf("%d", &i);
    printf("请输入您要在第%d个节点插入的值\n", i);
    scanf("%d", &j);
    insert_list(phead, i, j);
    traverse_list(phead);
    printf("请输入您要第几个删除的节点\n");
    scanf("%d", &i);
    val = delete_list(phead, i, val);
    printf("您删除的节点值为: %d\n", val);
    traverse_list(phead);
    freeer(phead);

    return 0;
}

pnode establish_list(void)//初始化链表,返回头结点地址
{
    int val, len;
    pnode tem;
    pnode pnew;
    pnode phead;

    phead = (pnode)malloc(sizeof(node));
    tem = phead;
    if(null == phead)
    {
        printf("分配失败");
        exit(-1);
    }
    tem->pnext = null;
    printf("请输入您要定义节点的长度: ");
    scanf("%d", &len);

    for (int i=0;idata = val;//首先把本次创建的新节点的值付给新节点的数据域
        tem->pnext = pnew;//然后使用临时的节点变量的指针域保存了新节点的地址,也就是指向了新节点
        pnew->pnext = null;//如何再不循环,新节点成为最后一个节点
        tem = pnew;//把本次分配的新节点完全的赋给tem,tem就成为了这次新节点的影子,那么下次分配新节点时可以使用上个新节点的数据
    }
    return phead;
}

void traverse_list(pnode phead)
{
    pnode p = phead;//使用p是为了不改写头结点里保存的地址
    p = phead->pnext;//使p指向首节点

    while(p != null)//p本来就是头结点的指针域,也就是首节点的地址,既然是地址就可以直接判断p是否等于null
    {
        printf("%d ", p->data);
        p = p->pnext;//使p每循环一次就变成p的下一个节点
    }
}

bool is_empty(pnode phead)
{
    if(null == phead->pnext)
        return true;
    else
        return false;
}

int length_list(pnode phead)
{
    pnode p = phead->pnext;
    int len = 0;

    while(p != null)
    {
        len++;
        p = p->pnext;
    }
    return len;
}

void sort_list(pnode phead)
{
    int i, j, t, len;
    pnode p, q;
    len = length_list(phead);

    for(i=0,p=phead->pnext;ipnext)//逗号后只是为了找到下一个节点,因为不是数组,所以不能使用下标来++
    {
        for(j=0,q=phead->pnext;jpnext)
            if(q->data > p->data)//这里的大小与号可以决定是升序还是降序,如果是大于号就是升序,反之小于号就是降序
            {
                t = q->data;
                q->data = p->data;
                p->data = t;
            }
    }

    return;
}

void insert_list(pnode phead, int pos, int val)
{
    int i;
    pnode q = phead;
    pnode p = phead;
    if(pos > 0 && pos <= length_list(phead))
    {
        for(i=0;ipnext;//q就是要插入的连接点
        }
        for(i=1;ipnext;//p就是要插入连接点的前一个节点
        }
        pnode pnew = (pnode)malloc(sizeof(node));
        p->pnext = pnew;
        pnew->data = val;
        pnew->pnext = q;
    }
    else if(pos > length_list(phead))//追加
    {
        pnode t;
        t = phead;
        pnode pn;
        pn = (pnode)malloc(sizeof(node));
        if(pn == null)
            printf("分配失败");
        else
            while(t->pnext != null)
            {
                t = t->pnext;//使t->pnext成为尾结点
            }
        pn->data = val;//给新节点赋予有效数据
        t->pnext = pn;//使尾结点的指针域指向了新的结点
        pn->pnext = null;//新节点成为尾结点
    }
    else
        printf("error\n");
    return;
}

int delete_list(pnode phead, int pos, int val)
{
    int i, j;
    pnode q, p;
    q = phead;
    p = phead;

    if(pos > 0 && pos <= length_list(phead))//保证删除的是节点的有效数
    {
        for(i=0;ipnext;
        }
        for(j=1;jpnext;
        }
        q->pnext = p->pnext;
        val = p->data;

        free(p);
        return val;
    }
    else
        printf("error");
    return 0;
}

void freeer(pnode phead)
{
    pnode pt = phead;
    while(null != phead->pnext)
    {
        free(pt);
        pt = pt->pnext;
    }

    return;
}
;j++)>;i++)>;i++)>;i++)>

C语言 - 链表的基本操作