数据结构之链表(c语言版)

链表是线性表,链表的特点就是可以动态增减元素。种类有单向链表、双向链表,循环链表

一、单链表

单链表的储存思想使用指针表示节点之间的逻辑关系,它的储存单元可以连续也可以不连续,每个储存单元需要储存信息和储存与后继节点的地址信息,储存单元又称之为节点。单链表由头指针唯一确定,整个单链表的操作必须由头指针开始。

链表的单位是一个一个节点,每个节点分为数据域和指针域,数据域存放数据,指针域存放指向下一个节点的指针(没有指针的语言存放的是对下一个节点的引用)。

头节点通常不放数据(也可以存放数据),尾节点指针域为空(循环链表不为空)。

单链表示意图:

在这里插入图片描述

c语言实现单链表1.0

还是1.0版本和2.0版本哈,2.0是后来增加的,更加健壮!

#include<stdio.h>
#include<stdlib.h>

//定义节点
typedef struct list
{   int data;
    struct list *next;
    /* data */
}Node;


//节点初始化
Node *create_node(){
    Node *p=(Node *)malloc(sizeof(Node));
    //节点
    p->next=NULL;
    return p;
}

//建立链表,尾部增加
Node *link_list(int d[],int size){
    Node *head=create_node();
    Node *tail=head;        //尾节点,此时尾节点就是头节点
    for(int i=0;i<size;i++){
        Node *p=(Node *)malloc(sizeof(Node));
        p->data=d[i];
        p->next=NULL;
        tail->next=p;
        tail=p;//p变为尾节点
    }

    return head;
}

//追加
int append(Node *head,int e){
    Node *p=create_node();
    p->data=e;
    Node *s=head;
    while(s->next!=NULL){
        s=s->next;
    }
    s->next=p;
    return 1;
}

//长度
int Len(Node *head){
    Node *p=head;
    int count=0;
    for(;p!=NULL;count++){
        p=p->next;
    }
    return count;
    
}

//插入
int Insert(Node *head,int i,int e){

    if(i<1){
        printf("插入错误,程序终止");
        exit(0);
    }
    if(i>=Len(head)+1){
        //此时为追加
        append(head,e);
        return 1;
    }
    
    
    int n=1;
    Node *p=head;
    
    for(;n<i;n++){
        p=p->next;
    }
    Node *s=create_node();
    s->data=e;
    s->next=p->next;
    p->next=s;
    return 1;

}

//删除
int Delete(Node *head ,int i){

    if(i<1||i>Len(head)){
        printf("删除非法,程序终止");
        exit(0);
    }


    Node *f=head;//前指针
    Node *b;//后指针


    for(int n=1;n<i;n++){
        f=f->next;
        b=(f->next)->next;
    }

    Node *s=f->next;//此为被销毁的节点

    f->next=b;

    free(s);//释放内存

    return 1;
}

//查找
int Query(Node *head ,int i){

    if(i<1||i>Len(head)){
        printf("查找非法,程序终止");
        exit(0);
    }

    Node *p=head;//工作指针


    for(int n=1;n<i;n++){
        p=p->next;
    }

    int e=p->data;//查询的元素

    return e;
}
//修改
int Update(Node *head ,int i,int e){

    if(i<1||i>Len(head)){
        printf("修改非法,程序终止");
        exit(0);
    }


    Node *p=head;//工作指针


    for(int n=1;n<i;n++){
       p=p->next;
    }

    p->data=e;

    return 1;
}

//判空
int is_Empty(Node *head){
    if(head->next==NULL){
        return 1;
    }
    return 0;
}

//遍历链表
void display(Node *head){


    Node *p=head->next;//头节点没有数据

    printf("此时有%d个节点\n",Len(head));

    for(;p!=NULL;p=p->next){
        printf("The data is %d\n",p->data);
    }
}

//销毁链表
void Destory(Node *head){

    for(Node *p=head;head!=NULL;){
        head=head->next;
        free(p);
        p=head;
    }
    printf("链表已经销毁\n");
    exit(0);

}


这里把大部分功能都用函数表达了,有些功能用属性似乎更加简便,不过无伤大雅。

在主函数中测试一下功能:

int main(){
    int a[5]={2,1,5,11,6};
    int size=sizeof(a)/sizeof(int);
    printf("创建链表\n ");
   
    Node *p=link_list(a,size);
    
    display(p);

    append(p,18);
    display(p);

    Insert(p,2,37);
    Insert(p,4,23);
    Insert(p,14,37);
    display(p);

    Delete(p,2);
    display(p);

    int i=9;
    int e=Query(p,i);
    printf("查询的是第%d个元素,值为%d\n",i,e);

    Update(p,i,10);
    printf("修改了%d号元素,现在值为%d\n",i,Query(p,i));

    Destory(p);
}

测试结果:

创建链表
 此时有6个节点
The data is 2
The data is 1
The data is 5
The data is 11
The data is 6
此时有7个节点
The data is 2
The data is 1
The data is 5
The data is 11
The data is 6
The data is 18
此时有10个节点
The data is 2
The data is 37
The data is 1
The data is 23
The data is 5
The data is 11
The data is 6
The data is 18
The data is 37
此时有9个节点
The data is 2
The data is 1
The data is 23
The data is 5
The data is 11
The data is 6
The data is 18
The data is 37
查询的是第9个元素,值为37
修改了9号元素,现在值为10
链表已经销毁

c语言实现单链表2.0

从全局静态变量来表示头节点和节点数量,此时代码逻辑简单多了。

增删改查功能都没有实现,可以自行摸索。

#include<stdio.h>
#include<stdlib.h>

//定义节点
typedef struct list
{   int data;
    struct list *next;
    /* data */
}Node;

static int count=0;//节点数量,代替了Len函数中的逻辑
static Node *head=NULL;//头节点


//节点初始化
Node *create_node(){
    Node *p=(Node *)malloc(sizeof(Node));
    //节点
    p->next=NULL;
    return p;
}

//建立链表,尾部增加
Node *link_list(int d[],int size){
    head=create_node();
    Node *tail=head;        //尾节点,此时尾节点就是头节点
    for(int i=0;i<size;i++){
        Node *p=create_node();
        p->data=d[i];
        p->next=NULL;
        tail->next=p;
        tail=p;//p变为尾节点
        printf("链表增加元素%d\n",d[i]);
    }

    count=size+1;//头节点也算
    printf("链表创建成功\n");
    return head;
}



//长度
int Len(){
    //原先的代码去掉,直接由count代替
    return count;
}



//判空
int is_Empty(){
    if(count==0){
        return 1;
    }
    return 0;
}

//遍历链表
void display(){


    printf("此时有%d个节点\n",count);
    Node *p=head->next;

    // for(;p!=NULL;p=p->next){
    //     printf("The data is %d\n",p->data);
    // }

    for(int i=1;i<count;i++){
        printf("The data is %d\n",p->data);
        p=p->next;
    }
}

int main(){
    int a[5]={2,1,5,11,6};
    int size=sizeof(a)/sizeof(int);
    printf("创建链表\n ");
   
    Node *p=link_list(a,size);
    display();
    
}

二、单向循环链表

循环链表有单向的也有双向的,其实就是尾节点的指针域指向了头节点,成为循环结构。这样从任意位置都可以遍历所有。

在这里插入图片描述

在单链表1.0的基础上稍作修改,一个单向循环链表就完成了。在指针的指向方面稍作修改就可以完成,部分方法就没有提供了,可以自行摸索。

单循环链表1.0

#include<stdio.h>
#include<stdlib.h>

//定义节点
typedef struct list
{   int data;
    struct list *next;
    /* data */
}Node;


//节点初始化
Node *create_node(){
    Node *p=(Node *)malloc(sizeof(Node));
    //节点
    p->next=NULL;
    return p;
}

//建立链表,尾部增加
Node *link_list(int d[],int size){
    Node *head=create_node();
    Node *tail=head;        //尾节点,此时尾节点就是头节点
    for(int i=0;i<size;i++){
        Node *p=(Node *)malloc(sizeof(Node));
        p->data=d[i];
        p->next=head;//指向头节点
        tail->next=p;
        tail=p;//p变为尾节点
    }

    return head;
}


//长度
int Len(Node *head){
    Node *p=head;
    if(p==NULL){
        return 0; 
    }
    p=head->next;
    Node *s=head;
    int count=1;
    for(;p!=s&&p!=NULL;count++){
        p=p->next;
    }
    return count;
    
}


//判空
int is_Empty(Node *head){
    if(head->next==NULL){
        return 1;
    }
    return 0;
}

//遍历链表
void display(Node *head){


    Node *p=head->next;//头节点没有数据
    Node *s=head;//标记指针

    printf("此时有%d个节点\n",Len(head));

    for(;p!=s;p=p->next){
        printf("The data is %d\n",p->data);
    }
}


在主函数中测试一下:

int main(){
    int a[5]={2,1,5,11,6};
    int size=sizeof(a)/sizeof(int);
    printf("创建链表\n ");
   
    Node *p=link_list(a,size);
    
    display(p);//遍历循环链表

    Node * s=(p->next)->next;//指向其他节点的指针

    display(s);//其他节点遍历
}

结果如下,可以看到不是预期的结果,某个数据。。。是1995024,这是个啥,这是头节点的data,自动分配的。

创建链表
 此时有6个节点
The data is 2
The data is 1
The data is 5
The data is 11
The data is 6
此时有6个节点
The data is 5
The data is 11
The data is 6
The data is 1995024
The data is 2

这肯定不行啊,说好的头节点不放数据的,所以我们遇到头节点得跳到下一级。1.0版本的可以自己实现一下!

我们搞个2.0版本的。

单循环链表2.0

#include<stdio.h>
#include<stdlib.h>

//定义节点
typedef struct list
{   int data;
    struct list *next;
    /* data */
}Node;

static Node *head=NULL;
static int count=0;


//节点初始化
Node *create_node(){
    Node *p=(Node *)malloc(sizeof(Node));
    //节点
    p->next=NULL;
    return p;
}

//建立链表,尾部增加
Node *link_list(int d[],int size){
    head=create_node();
    Node *tail=head;        //尾节点,此时尾节点就是头节点
    for(int i=0;i<size;i++){
        Node *p=create_node();
        p->data=d[i];
        p->next=head;//指向头节点
        tail->next=p;
        tail=p;//p变为尾节点
    }

    count+=size+1;//节点数量
    return head;
}

//查询
int query(int i){
    is_error(i);

    Node *p=head;

    for(int n=1;n<i;n++){
        p=p->next;
    }
    return p->data;

}

//是否非法
int is_error(int i){
 if(is_Empty()){
        printf("NULL,非法\n");
        return 1;
    }
    if(i<1||i>count){
        printf("查询超长,非法\n");
        return 2;
    }

    return 0;
}


//长度
int Len(){

    return count;
}


//判空
int is_Empty(){
    if(count==0){
        return 1;
    }
    return 0;
}


//遍历链表
void display(Node *p){


    printf("此时有%d个节点\n",count);
   
        for(int i=1;i<count;i++){
            if(p==head){	//判断是否是头指针
                p=p->next;
            }
            printf("The data is %d\n",p->data);
            p=p->next;
        }
    

}


主函数中测试一下:

int main(){
    int a[5]={2,1,5,11,6};
    int size=sizeof(a)/sizeof(int);
    printf("创建链表\n ");
   
    Node *p=link_list(a,size);
    
    display(p);//遍历循环链表

    p=p->next->next;//偏移

    display(p);//其他节点遍历
}
创建链表
 此时有6个节点
The data is 2
The data is 1
The data is 5
The data is 11
The data is 6
此时有6个节点
The data is 1
The data is 5
The data is 11
The data is 6
The data is 2

此时结果应该是正确的!

三、双向链表

双向链表即有两个指向的链表,通常指指向前节点和后继节点的链表。这样也是从任何部位都能遍历全部元素,

且查找元素时,可以向前查找或者向后查找,不必完全遍历全链表。

双向链表示意图:

双向链表

c语言实现双链表

在单链表2.0的基础上研究双链表。

先创建一个双向链表。

#include<stdio.h>
#include<stdlib.h>

//定义节点
typedef struct list
{   int data;
    struct list *next;//双指针
    struct list *prev;
    /* data */
}Node;


static int count=0;//节点数量

static Node *head=NULL;//头节点


//节点初始化
Node *create_node(){
    Node *p=(Node *)malloc(sizeof(Node));
    //节点
    p->next=NULL;
    p->prev=NULL;
    return p;
}


//尾节点
Node *get_tail(){
    Node *tail=head;

    for(int i=1;i<count;i++){
        tail=tail->next;
    }

    return tail;
}

//建立链表,尾部增加
Node *link_list(int d[],int size){
    head=create_node();//头节点初始化
    Node *tail=head;        //尾节点,此时尾节点就是头节点
    for(int i=0;i<size;i++){
        Node *p=create_node();
        p->data=d[i];
        tail->next=p;
        p->prev=tail;
        tail=p;//p变为尾节点
    }
    count=size+1;
    return head;    //返回头节点
}


//长度
int Len(){

    return count;
}


//判空
int is_Empty(){
    if(count==0){
        return 1;
    }
    return 0;
}

//遍历链表,从头节点开始
void display(){

    printf("此时有%d个节点\n",count);

    Node *p=head->next;

    for(;p!=NULL;p=p->next){
        printf("The data is %d\n",p->data);
    }
}

//查找元素
//查找元素
Node* query(int i){
    if(error(i)){
        return NULL;
    }
     Node *p;
    if(i<=(int)(count/2)){//从头结点开始查找
       p=head;
        for(int n=1;n<i;n++){
            p=p->next;
        }
        return p;

    }else                   //从尾节点向前查找
    {
        p=get_tail();
        for(int n=0;n<count-i;n++){
            p=p->prev;
        }
        return p;   
    }
    return NULL;
}

//得到节点
Node *get_node(int i){
    Node *p=head;
    if(i==count){
        return get_tail();
    }
     if(error(i)){
        return NULL;
    }
    for(int s=1;s<i;s++){
        p=p->next;
    }
    return p;
}

//判断传入参数是否非法
int error(int i){
    if(is_Empty()){
        printf("链表NULL,非法\n");
        return 1;
    }
    if(i<1||i>count){
        printf("查询超长,非法\n");
        return 2;
    }

    return 0;
}



测试一下:

int main(){
    int a[5]={2,1,5,11,6};
    int size=sizeof(a)/sizeof(int);
    printf("创建链表\n ");
   
    Node *p=link_list(a,size);
    display();

    
    printf("查询的元素是%d\n",query(5)->data);
    printf("查询的元素是%d\n",query(2)->data);
    
 
}

此时可以查询。

创建链表
 此时有6个节点
The data is 2
The data is 1
The data is 5
The data is 11
The data is 6
查询的元素是11
查询的元素是2

再加入删除、更新、插入函数。

//插入
void insert(int i,int e){

    Node *s =query(i);

    Node *p=create_node();
    p->data=e;
    p->next=s;
    p->prev=s->prev;
    (s->prev)->next=p;
    s->prev=p;
    count++;
}
//删除
void delete(int i){
    Node *s=query(i);
    Node *p,*n;
    //修改指针指向
    p=s->prev;
    n=s->next;
    p->next=n;
    n->prev=p;
    free(s);    //销毁节点
    count--;//数量减少
}
//更新
void update(int i,int e){
    Node *p=query(i);
    p->data=e;
}

再次测试:

int main(){
    int a[5]={2,1,5,11,6};
    int size=sizeof(a)/sizeof(int);
    printf("创建链表\n ");
   
    Node *p=link_list(a,size);
    display();


    printf("查询的元素是%d\n",query(5)->data);
    printf("查询的元素是%d\n",query(2)->data);

    update(2,8);
    display();

    delete(2);
    display();

    insert(2,8);
    display();

}

如下所示:

创建链表
 此时有6个节点
The data is 2 
The data is 1 
The data is 5 
The data is 11
The data is 6
查询的元素是11
查询的元素是2
此时有6个节点
The data is 8
The data is 1
The data is 5
The data is 11
The data is 6
此时有5个节点
The data is 1
The data is 5
The data is 11
The data is 6
此时有6个节点
The data is 8
The data is 1
The data is 5
The data is 11
The data is 6

四、双向循环链表

双向循环链表的用处比较广泛、不过就是双向链表的基础上进行了循环。构造难度不大,可以参考单链表循环实现。

数据结构与算法学习目录

posted @ 2024-04-11 11:32  cgl_dong  阅读(917)  评论(0编辑  收藏  举报