数据结构--链表

首先介绍一下抽象数据类型(Abstract Data Type, ADT), 它是一些操作的集合,但未定义如何实现这些操作,例如表,集合,图以及它们的操作都可以看作为 ADT 。对于一个集合 ADT,我们可以编写一些自己想要用到的操作,并且这些操作只在程序中编写一次,在程序中的其它部分需要用到某些操作(比如求大小,求并等)的时候,通过直接调用其中的函数来达到目的。
(对表的操作都可以通过使用数组(线性的)来实现,但是简单数组一般不用来实现表这种结构(某些操作的运行缓慢,若表的大小未知的前提下,通常需要开一个大数组,这会浪费大量的空间)

接下来说一下链表
链表就是一些包含数据的独立数据结构(节点)的集合(可以不连续存储),也就是由一系列不必在内存中相连的节点组成,每一个节点均含有表元素和指向下一个节点的指针,最后一个节点的指针指向 NULL , 该值由 C 定义且不能与其它指针混淆,在ANSI C 中定义 NULL 为 0。由于节点通过链或指针连接起来,我们可以通过指针来访问链表中的节点(通过指针来遍历链表)。
为了记住链表的起始位置,可以使用一个根指针(root pointer)(不包含任何数据),根指针指向链表的第一个节点。
Ex:下面这个表包含 3 个结构,内存中分配给它们的地址分别为1200, 1000 ,800 和 990。
link value link value link value link
| 800 | | A1 | 800 | | A2 | 990 | | A3 | 0 |
| 1200 | | 1000 | | 800 | | 990 |
假如现在需要执行一条删除命令,可以直接通过修改指针来实现,删除之后再将该单元释放通常是一个好想法; 若执行一条插入命令(A2 和 A3),需要使用一次 malloc 调用从系统中得到一个新单元并执行两次指针调整 (A2 中的指针地址改成新单元的地址,新单元的指针地址改成A3 )。
上面所说的是单链表,单链表可以通过链从表头遍历到表尾,但是无法反向遍历,当然可以通过保存上一个节点地址来访问,但是链表是动态分配的,当节点数增长到一定时,保存所有的节点地址是不可行的。想要倒序扫描链表的解决办法很简单,只需要在每一个节点上附加一个域,使它指向前一个节点的指针即可,也就是双链表。另外让最后一个节点指向第一个节点,那么这个链表既可以有表头,也可以没有表头,称为循环链表。

可以看看下面这段一个有序(升序)单链表插入的程序:
对于一个节点作出如下定义:

typedef struct NODE{
    struct NODE *link;
    int         value;
}Node;

我们对于一个新值插入的情况来分析,会出现三种情况:

  1. 插入到链头;
  2. 插入到中间;
  3. 插入到链尾;
/*
插入到一个有序的单链表,函数的参数是一个指向链表第一个节点的指针以及需要插入的值
*/

int
sll_insert( register Node **linkp, int new_value )
{
    register Node *current;
    register Node *new;
    
    /*
    **寻找正确的插入位置,方法是按照顺序访问链表,直到到达其值大于或等于新插入值的节点
    */
    while( ( current = *linkp ) != NULL && current -> value < new_value )
        linkp = &current -> link;
  
    /*
    **为新节点分配内存,并把新值存储到新的节点中,如果内存分配失败,函数返回FALSE
    */
    new = (Node *)malloc( sizeof( Node ) );
    if( new == NULL )
        return FALSE;
    new -> value = new_value;

	/*
    **把新节点插入到链表中,并返回 TURE
    */
    new -> link = current;
    *linkp = new;
    return TRUE;
}

对于单链表来说只需要一个根指针(只能单向遍历),对于双链表来说,可以使用两个根指针(双向遍历),一个指向链头,一个指向链尾。
此外,为根指针声明一个完整的节点比分开声明两个变量更为方便,对于一个节点作出如下定义:

typedef struct NODE {
        struct NODE *fwd;       //
        struct NODE *bwd;
        int         value;
}Node;

对于根节点来说,fwd 字段指向链头,bwd 字段指向链尾,两个都为 NULL 时,链表为空。
此外,第一个节点的 bwd 和 最后一个字节的 fwd 都为 NULL。

下面我们来看看在双链表中插入的情况:
我们对于一个新值插入的情况来分析,会出现四种情况:

  1. 插入到链头;
  2. 插入到中间;
  3. 插入到链尾;
  4. 既插入到链头,又插入到链尾(即链表为空);
    直接看一下程序:
/*
**把一个值插入到一个双链表,rootp是一个指向根节点的指针
**value是欲插入的新值
**返回值:如果欲插值原先已经存在于链表中,函数返回 0 
**如果内存不足导致无法插入,函数返回-1,如果插入成功返回 1 
*/

int 
dll_insert(Node *rootp. int value)
{
    Node *this;
    Node *next;
    Node *newnode;

    /*
    **查看value是否已经存在于链表中,如果是就返回,否则,为新值创建一个新的节点(newnode将指向它)
    **"this"将指向应该在新节点之前的那个节点
    **"next"将指向应该在新节点之后的那个节点
    */
    for( this = rootp; (next = this->fwd) != NULL; this = next){
        if( next -> value == value )
            return 0;
        if( next -> value > value )
            break;
    }
    newnode = (Node *)malloc( sizeof( Node ));
    if( newnode == NULL )
        return -1;
    newnode -> value = value;

    /*
    **把新值添加到链表中
    */
    newnode -> fwd = next;
    this -> fwd = newnode;

    if(this != rootp)
        newnode -> bwd = this;
    else
        newnode -> bwd = NULL;
    if( next != NULL )
        next -> bwd = newnode;
    else
        rootp -> bwd = newnode;
    return 1;
} 
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ZSTU_呆大鹅

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值