C 项目案例实践(1)数据结构之链表(0)

链表是通过一组任意的存储单元来存储线性表中的数据元素的,那么怎样表示出数据元素之间的线性关系呢?为建立数据元素之间的线性关系,对每个数据元素ai,除了存放数据元素的自身信息ai之外,还需要存放和ai一起存放其后继ai+1所在的存储单元的地址,这两部分信息组成一个"节点"(如图),存放数据元素信息的称为数据域,存放其后继地址的称为指针域。因此,n个元素的线性表通过每个节点的指针域拉成了一条"链子", 称之为链表。因为每个节点中只有一个指向后继的指针,所以称之为单链表。

image

链表是由一个个节点构成,节点定义如下:

typedef   struct   node

{

datatype data;

struct node *next;

}LNode, *LinkList;

定义指针变量: LinkList H;

下图分别是带头节点的单链表空表和非空表的示意图

image

注:LNode是节点的类型,LinkList是指向LNode类型节点的指针类型。

在操作中需要用到某节点的指针变量时, 做如下声明是等价的。

LNode *p;  等价于  LinkList H;

p  = malloc(sizeof(LNode)); 表示申请一块LNode类型的存储单元,并将这块存储空间的地址赋值给变量p. p所指的节点为*p, *p的类型为LNode型,所以该节点的数据域为(*p).data 或 p->data, 指针域为(*p)->next, 或 p->next。

存储空间的分配和释放

1.存储空间分配函数原型: void *malloc(unsigned int size);

作用是在内存中动态获取一个大小为size个字节的连续的存储空间。并返回一个void类型的指针,若分配成功,该指针指向已分配空间的起始地址,否则,该指针将为空(NULL)

2.连续空间分配函数原型: void *calloc(unsigned, n unsigned size);

作用是在内存中动态获取n个大小为size个字节的连续的存储空间。 该函数将返回一个void类型指针,若分配成功,该指针指向已分配空间的首地址,否则返回空(NULL)。用calloc()可以动态获取一个一维数组空间,其中n为数组元素个数,每个数组元素的大小为size个字节。

3.空间释放函数原型:void free(void *addr);

free()的作用是释放由addr指针所指向的空间,即系统回收,使这段空间又可以被其它变量所用。

建立和输出链表

所谓动态建立链表是指在程序执行过程中从无到有地建立链表,将一个个新生成的节点依次链接入已建立起来的链表上。上一个节点的指针域存放下一个节点的起始地址,并给各个节点数据域赋值。

例如,建立一个学生成绩的链表,其节点的结构体定义如下:

struct student
{
    long num;
    char name[20];
    float score;
    struct student *next;
};

4步建立链表:

1.定义三个指针变量,head头指针,p1指向新节点,p2指向尾节点

2.产生一个节点, head , p1和p2都指向它,并输入想要的数据。

3.循环操作,陆续产生新节点,输入数据,链接到表尾

4.最后,尾节点指针域置空,返回头指针。

create()函数创建链表, 返回链表头指针:

struct student *create()
{
    struct student *p1, *p2, *head;
    int i, n = 2;
    head = NULL;
 
    head = p1 = p2 = (struct student *) malloc(sizeof(struct student));
 
    if(!head) return false; //检测内存空间是否申请成功
 
    printf("input num  name  score
");
    scanf("%ld%s%f",&p1->num,&p1->name,&p1->score);
 
    for(int i = 1; i<n; i++)
    {
        p1= (struct student *)malloc(sizeof(struct student));
 
        if(!p1) return false;
 
        scanf("%ld%s%f",&p1->num,&p1->name,&p1->score);
        p2->next = p1;
        p2 = p1;
    }
    p2->next = NULL;
    return head;
}

利用print()函数输出链表数据:

void print(struct student *p)
{
    while(p != NULL)
    {
        printf("ID: %ld Name: %10s  score:%6.2f
",p->num,p->name,p->score);
        p=p->next;
    }
}

最后在main函数中完成调用工作:

int main(void)
{
    struct student *head;
    head = create();
    print(head);
 
    fflush(stdin);
    getchar();
 
}

单链表的基本操作

1.插入。

如果要在单链表的两个数据元素之间插入一个数据元素x,已知p为其单链表存储结构中指向节点a的指针(如图(a)),为插入数据元素x,首选要生成一个数据域为x的节点,然后插在单链表中,插入前需要修改节点a中的指针域,令其指向节点x,而节点x中的指针域应指向节点b,从而实现三个元素a,b和x之间逻辑关系的变化。插入单链表如图(b),假设s为指向节点x的指针,则上述过程可表述如下:

s->next = p->next;

p->next = s;

image

图(a)

image

图(b)

2.删除

要删除单链表中的元素x,仅需要把x节点的指针域目前保存的它的下一个节点b的地址赋值给a节点的指针域即可。 假设p,q分别是指向a,x节点的指针,则:

p->next = q->next;

free(q);

3.查找

链表查找是指在链表中查找某成员值为给定值的节点。下面定义一个查找函数,它的返回值即为指向查找到的节点的指针。查找方法是先输入要查找的给定值,然后从链表的头指针所指的第一个节点开始,按链接顺序逐一比较:当查找到给定值的节点时,则返回该节点,否则返回空指针。

struct student *find(struct student *p)
{
    long num;
    printf("Input the std ID :");
    scanf("%ld",&num);
 
    while(p != NULL)
        {
            if(num == p->num) return p;
            p = p->next;
        }
    return NULL;
}

双链表

单链表的节点中只有一个指向其后继节点的指针域next, 因此,若已知某节点的指针为p, 其后继节点的指针则为p->next, 而找其前驱则只能从该链表的头指针开始,顺着各节点的next域进行,也就是说找后继的时间性能是O(1),而找前驱的时间性能是O(n) , 如果希望找前驱的时间性能达到O(1),则只能付出空间代价:每个节点再加一个指向前驱的指针域,节点的结构如图(1),用这种节点组成的链表称为双向链表:

image

双链表节点的定义如下:

typedef struct dlnode
{
    datatype data;
    struct dlnode *prior , *next;
}DLNode , *DLinkList;
与单链表类似,双向链表通常也是用头指针标识,也可以带头节点和作成循环结构,图2是带头节点的双向链表图。显然通过某节点的指针p即可以直接得到它的后继节点指针p->next和前驱节点p->prior. 假设p是指向双向循环链表中的某一节点的指针,则p->prior->next表示的是*p节点的前驱节点的后继节点的指针,即与p相等。
image
双向链表中节点的插入:设p指向双向链表中某节点,s指向待插入的值为x的新节点,将*s插入到*p的前面,插入如图3, 操作如下:
1. s->prior = p->prior;
2. p->prior->next = s;
3. s->next = p;
4. p->prior = s;
image
双向链表中节点的删除,蛇p指向双向链表中某节点,删除*p;
1.p->prior->next = p->next;
2. p->next->prior = p->prior;
3. free(p ) ;
 
原文地址:https://www.cnblogs.com/AI-Algorithms/p/3383839.html