c-数据结构(顺序表、链表)

概念

对于n各元素的线性表,严格数学定义:其中任意一个数据元素a[i],有且仅有一个前驱a[i-1],有且仅有一个后继a[i+1];首元素a[0]无前驱,尾元素a[n-1]无后继。

顺序表

属于线性表,数据之间的空间是连续,如具名的栈数组,匿名的堆数组。

链表

属于线性表,数据之间的空间不连续(离散),如结构体。

单链表
  • 顺序表设计

    1. 顺序表容量;

    2. 顺序表当前最末元素下标位置;

    3. 顺序表指针;

    顺序表
cpp 复制代码
顺序表的相关数据操作代码
//math文件夹
------------------------------------------------------------------------------------------
seqlist.h
------------------------------------------------------------------------------------------
#ifndef __SEQLIST_H
#define __SEQLIST_H
    
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
    
//定义顺序表的结构体
typedef struct
{
    int capacity;	//顺序表的容量(本质为数组);
    int last;		//末元素下标
    int* data;		//数据
} seqList;

//创建顺序表
seqList *initList(int cap);

//判断顺序表是否满了
bool isFull(seqList *list);

//向顺序表表头插入新数据
bool insert(seqList *list, int data);

//遍历顺序表元素
void show(seqList *list);

//删除顺序表指定元素
bool removeNode(seqList *list, int data);

//销毁顺序表
void destroy(seqList *list);
#endif
------------------------------------------------------------------------------------------
seqlist.c
------------------------------------------------------------------------------------------ 
#include "seqlist.h"
    
//初始化顺序表
seqList *initList(int cap)
{
    //申请内存
    seqList *list = malloc(sizeof(seqList));
    
    //顺序表非空校验
    if(list != NULL)
    {
        //给数据区分配空间
        list->data = malloc(cap * sizeof(int));
        
        if(list->data == NULL)
        {
            free(list);
            return NULL;
        }
        
        list->capacity = cap;
        list->last = -1;
    }
}

//判断顺序表是否满了
bool isFull(seqList *list)
{
    return list->last == list->capacity - 1;	//尾结点与下标的对应关系
}
 
//向顺序表表头插入新数据
bool insert(seqList *list, int data)
{
    //判断顺序表是否满了
    if(isfull(list))
    {
        return false;
    }
    //将原有数据全部向后移动一位,腾出空间
    for(int i = list->last; i >= 0; i--)
    {
        list->data[i+1] = list->data[i];
    }
    //将数据插入表头
    list->data[0] = data;
    list->last++;
    
    return true;
}
 
//遍历顺序表元素
void show(seqList *list)
{
    //判断是否为空
    if(list->lats == -1)
    {
        return;
    }
    //遍历
    for(int i = 0; i <= list->last; i++)
    {
        printf("%d ", list->data[i]);
    }
    printf("\n");
}
 
//删除顺序表指定元素
bool removeNode(seqList *list, int data)
{
    //判断顺序表是否为空
    if(isFull(list))
    {
        return false;
    }
    //找到要产出元素结点
    int pos;
    for(int i = 0; i <= list->last; i++)
    {
        if(list->data[i] == data)
        {
            pos = i;
            break;
        }
    }
    //若找不到要删除的的节点
    if(i > list->last)
    {
        return false;
    }
    //将截至到删除节点后所有元素前移
    for(int i = pos; i < list->last; i++)
    {
        list->data[i] = list->data[i+1];
    }
    list->last--;
    return true;
}

//销毁顺序表
void destroy(seqList *list)
{
    if(list == NULL)
    {
        return;
    }
    free(list->data);	//销毁顺序表中的是数据
    free(list);			//销毁顺序表
}

------------------------------------------------------------------------------------------
seqlist_main.c
------------------------------------------------------------------------------------------
#include "seqlist.h"
    
int main()
{
    //创建顺序表
    seqList *list = initList(10);
    
    if(lits == NULL)
    {
        perror("初始化顺序表失败!");
        //结束进程
        exit(0);
    }
    else
    {
        printf("初始化顺序表成功!");
    }
    
    int n;	//键盘录入整数
    printf("请输入人一个整数:\n");
    while(true)
    {
        scanf("%d", &n);
        //控制循环跳出
        if(n == 0)
        {
            printf("循环跳出\n");
            break;
        }
        else if(n > 0)
        {
            //插入数据
            if(!insert(list,n))
            {
                printf("容量已满,插入失败!\n");
                continue;
            }
        }
        else
        {
            //删除数据
            if(!removeNode(list,-n))
            {
                printf("查无此数,删除失败!\n");
                continue;
            }
        }
        //遍历顺序表
        show(list);
    }
    //销毁
    destroy(list);
}
顺序表优缺点
  • 优点

    1. 无需多余信息来记录数据间关系,数据存储密度高;

    2. 存储空间连续,随机访问速度快;

  • 缺点

    1. 插入删除数据时,需要成片移动数据,很不方便;

    2. 数据节点较多时,需要一整片较大连续空间;

    3. 数据节点数量变化剧烈,内存的释放和分配不灵活;

  • 单链表基本操作

    1. 节点设计;

    2. 初始化空链表;

    3. 增删节点;

    4. 链表遍历;

    5. 销毁链表;

单链表
cpp 复制代码
单链表相关数据操作代码
//math1文件夹
----------------------------------------------------------------------------------------------
slist.h
----------------------------------------------------------------------------------------------
#ifndef	__SLIST_H
#define __SLIST_H

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

typedef int DATA;

//创建单链表的结构体
typedef struct Node
{
    DATA data;			//存储数据
    struct Node *next;	//存储下一节点地址
}NODE;

//初始化单链表结构体
int slist_create(NODE**, DATA);

//向单链表插入一个数据(头插法)
int slist_addHead(NODE**, DATA);

//向单链表插入一个数据(尾插法)
int slist_addTail(NODE**, DATA);

//向单链表插入一个数据(中间插法)
int slist_insert(NODE**, DATA, DATA);

//删除单链表中的数据
int slist_delete(NODE**, DATA);	参考中间插法

//查找链表中的数据
NODE* slist_find(const NODE*, DATA);

//修改更新链表数据
int slist_update(const NODE*, DATA, DATA);

//遍历链表
void slist_showAll(const NODE*);

//销毁单链表
void slist_destroy(NODE**);

#endif
----------------------------------------------------------------------------------------------
slist.c
----------------------------------------------------------------------------------------------
#include "slist.h"


/*
@function:   int slist_create(NODE** head,DATA data);
@berif:     创建单项链表
@argument:   head: 指向头指针变量的地址,用来接收首节点地址
             data: 存储在节点中的数据
@return :   成功返回 0
             失败返回 -1
*/
//初始化单链表结构体
int slist_create(NODE** head, DATA data)
{
    NODE* p = (NODE*)malloc(sizeof(NODE));
    //非空校验
    if(!p)
    {
        return -1;
    }
    //单链表赋初值
    p->data = data;
    p->next = NULL;

    *head = p;
    return 0;
}


/*
@function:   int slist_addHead(NODE** head,DATA data);
@berif:     向链表头部插入一个节点数据
@argument:   head: 指向头指针变量的地址,用来接收首节点地址
             data: 存储在新节点中的数据
@return :   成功返回 0
             失败返回 -1
*/
//向单链表插入一个数据(头插法)
int slist_addHead(NODE** head, DATA data)
{
    NODE* p = (NODE*)malloc(sizeof(NODE));
    if(!p)
    {
        return -1;
    }
    p->data = data;
    p->next = *head;

    *head = p;
    return 0;
}


/*
@function:   int slist_addTail(NODE** head,DATA data);
@berif:     向链表尾部插入一个节点数据
@argument:   head: 指向头指针变量的地址,用来接收首节点地址
             data: 存储在新节点中的数据
@return :   成功返回 0
             失败返回 -1
*/
//向单链表插入一个数据(尾插法)
int slist_addTail(NODE** head, DATA data)
{
    NODE* pNew = (NODE*)malloc(sizeof(NODE));
    if(!pNew)
    {
        return -1;
    }
    pNew->data = data;
    pNew->next = NULL;

    //寻找尾节点,默认头节点为尾节点,并创建临时变量q
    NODE *p = *head, *q = NULL;
    if(!p)
    {
        *head = pNew;
        return 0;
    }

    while(p)
    {
        q = p;
        p = p->next;
    }

    q->next = pNew;	//将要插入的数放在原先尾节点的后面作为新的尾节点

    return 0;
}


/*
@function:   int slist_insert(NODE** head,DATA pos ,DATA data);
@berif:     向链表节点值为pos的位置插入一个节点数据data
@argument:   head: 指向头指针变量的地址
             pos: 插入节点位置的节点数据
             data: 存储在新节点中的数据
@return :   成功返回 0
             失败返回 -1
*/
//向单链表插入一个数据(中间插法)
int slist_insert(NODE** head, DATA pos, DATA data)
{
    //创建新节点
    NODE *pNew = (NODE*)malloc(sizeof(NODE));
    if(!pNew)
    {
        return -1;
    }
    //给新结点赋值
    pNew->data = data;
    pNew->next = NULL;

    NODE *p = *head, *q = NULL;
    //第一种情况,若原链表无任何一个节点
    if(!p)
    {
        *head = pNew;	//新节点作为头节点
        return 0;
    }
    //第二种情况,原链表只有一个头节点
    if(memcmp(&(p->data),&pos,sizeof(DATA)) == 0)
    {
        pNew->next = *head;		//头插法
        *head = pNew;
        return 0;
    }
    //第三种情况,原链表有多个节点
    while(p)
    {
        //尾插法
        if(memcmp(&(p->data),&pos,sizeof(DATA)) == 0)
        {
            //两句顺序不可调换,否则会覆盖
            pNew->next = p;	
            q->next = pNew;
            return 0;
        }
        //此时,q为前驱,p为后继;依次往后走
        q = p;
        p = p->next;
    }
    //第四种情况,要插入的位置pos不存在
    q->next = pNew;
    return 0;
}


/*
@function:   int slist_delete(NODE** head,DATA data);
@berif:     删除链表中节点值为data的节点
@argument:   head: 指向头指针变量的地址
             data: 删除节点中的数据
@return :   成功返回 0
             失败返回 -1
*/
//删除单链表中的数据
int slist_delete(NODE** head, DATA data)
{
	NODE* p = *head, *q = NULL;
    
    //第一种情况,若原链表无任何一个节点
    if(!p)
    {
        return -1;
    }
    //第二种情况,要删除的刚好是头节点
    if(memcmp(&(p->data),&data,sizeof(DATA)) == 0)
    {
        *head = p->next;
        free(p);
        return 0;
    }
    //第三种情况,逐个查找要删除的节点
    while(p)
    {
        if(memcmp(&(p->data),&data,sizeof(DATA)) == 0)
        {
            q->next = p->next;
            //这一步释放p并不影响数据的删除
            free(p);
            return 0;
        }
        q = p;
        p = p->next;
    }
    return -1;
}


/*
@function:   NODE* slist_find(const NODE* head,DATA data);
@berif:     查找链表数据data
@argument:   head: 指向头指针变量
             data: 待查找的数据
@return :   成功返回节点的地址
             失败返回NULL
*/
//查找链表中的数据
NODE* slist_find(const NODE* head, DATA data)
{
	const NODE* p = head;
    
    while(p)
    {
        if(memcmp(&(p->data),&data,sizeof(DATA)) == 0)
        {
            return (NODE*)p;
        }
        p = p->next;
    }
    return NULL;
}


/*
@function:   int slist_update(const NODE* head,DATA old,DATA newdata);
@berif:     更新链表数据old 为 newdata
@argument:   head: 指向头指针变量
             old: 待更新的节点数据
             newdata: 更新后的节点数据
@return :   成功返回 0
             失败返回 -1
*/
//修改更新链表数据
int slist_update(const NODE* head, DATA old_data, DATA new_data)
{
    NODE* p = NULL;
    
    if( !(p = slist_find(head,old_data)) )
    {
        return -1;
    }
    p->data = new_data;
    return 0;
}


/*
@function:   void slist_showAll(const NODE* head);
@berif:     遍历链表数据
@argument:   head: 指向头指针变量
@return :   无
*/
//遍历链表
void slist_showAll(const NODE* head)
{
    const NODE* p = head;

    while(p)
    {
        printf("%d ", p->data);
        p = p->next;
    }
    printf("\n");
}


/*
@function:   void slist_destroy(NODE** head);
@berif:     回收链表
@argument:   head: 指向头指针变量的地址
@return :   无
*/
//销毁单链表
void slist_destroy(NODE** head)
{
    NODE* p = *head, *q = NULL;

    while(p)	//逐个遍历回收
    {
        q = p;
        p = p->next;
        free(q);
    }
    *head = NULL;
}
----------------------------------------------------------------------------------------------
slist_main.c
----------------------------------------------------------------------------------------------
#include "slist.h"
#define DELETE 1

int main()
{
    NODE *head = NULL;

    if (slist_create(&head, 888) < 0)
    {
        perror("单链表创建失败!");
        // exit(0);
        return -1;
    }
    printf("单链表创建成功!\n");

    slist_addTail(&head, 999);
    slist_addTail(&head, 222);
    slist_addTail(&head, 666);
    slist_addTail(&head, 777);	// 运行效果:999 222 666 777

    slist_addHead(&head, 555);	// 运行效果:555 999 222 666 777

    slist_insert(&head, 555, 1024);	// 运行效果:1024 555 999 222 666 777
    slist_insert(&head, 222, 1080);	// 运行效果:1024 555 999 1080 222 666 777
    slist_showAll(head);

    DATA data;

    while (1)
    {
#ifdef DELETE
        printf("请输入要删除的数据:");
        scanf("%d", &data);
        if (data == -1)
            break;
        if (slist_delete(&head, data) < 0)
        {
            puts("删除失败,请重试");
            continue;
        }
        slist_showAll(head);
#else
        NODE *pFind = NULL;
        DATA newdata = 512;

        printf("请输入要查找的数据:");
        scanf("%d", &data);
        if (data == -1)
            break;
        // if(!(pFind = slist_find(head,data)))
        if (slist_update(head, data, newdata) == -1)
        {
            puts("查找的数据不存在,请重试");
            continue;
        }
        // printf("查找数据:%d  内存地址:%p\n",pFind -> data, &(pFind -> data));
        slist_showAll(head);
#endif
    }

    slist_destroy(&head);
    puts("====销毁后====");
    slist_showAll(head);

    return 0;
}
双链表
cpp 复制代码
双链表相关数据操作代码
//math2文件夹
----------------------------------------------------------------------------------------------
dlist.h
----------------------------------------------------------------------------------------------
#ifndef __DLIST_H
#define __DLIST_H

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

typedef int DATA;

//定义双向链表结构体
typedef struct node
{
    DATA data;          //数据
    struct node *prev;  //前驱指针
    struct node *next;  //后继指针
}NODE;

//创建双向链表
int dlist_create(NODE**, DATA);

//向双链表插入数据(头插法)
int dlist_addHead(NODE**, DATA);

//向双链表插入数据(尾插法)
int dlist_addTail(NODE**, DATA);

//向双链表插入数据(中间插法)
int dlist_insert(NODE**, DATA, DATA);

//链表数据查询
NODE* dlist_find(const NODE*, DATA);

//链表数据更新
int dlist_update(const NODE*, DATA, DATA);

//链表数据遍历
void dlist_showAll(const NODE*);

//链表数据删除
int dlist_delete(NODE**, DATA);

//销毁双链表
void dlist_destroy(NODE**);

#endif
----------------------------------------------------------------------------------------------
dlist.c
----------------------------------------------------------------------------------------------
#include "dlist.h"

//创建双向链表
int dlist_create(NODE** head, DATA data)
{
    //创建新节点
    NODE* pNew = (NODE*)malloc(sizeof(NODE));
    //判断是否申请成功
    if(!pNew)
    {
        return -1;
    }
    //给节点赋初值
    pNew->data = data;              //数据
    pNew->prev = pNew->next = NULL; //前驱后继指向空
    
    *head = pNew;                   //新节点作为头节点

    return 0;
}


//向双链表插入数据(头插法)
int dlist_addHead(NODE** head, DATA data)
{
    //创建新节点,申请内存空间
    NODE *pNew = (NODE*)malloc(sizeof(NODE));
    //判断是否申请成功
    if(!pNew)
    {
        return -1;
    }
    //给新节点赋值
    pNew->data = data;
    pNew->prev = NULL;
    pNew->next = *head;

    //如果头节点存在
    if(*head)
    {
        (*head)->prev = pNew;
    }

    *head = pNew;

    return 0;
}


//向双链表插入数据(尾插法)
int dlist_addTail(NODE** head, DATA data)
{
    //创建新节点,申请内存
    NODE *pNew = (NODE*)malloc(sizeof(NODE));
    //判断是否申请成功
    if(!pNew)
    {
        return -1;
    }
    //给新节点赋初值
    pNew->data = data;
    pNew->next = pNew->prev = NULL;

    NODE *p = *head;
    //第一种情况,头节点不存在
    if(!p)
    {
        *head = pNew;
        return 0;
    }
    //第二种情况,有一个或多个结点
    while (p->next) //循环来找到尾结点
    {
        p = p->next;
    }

    //尾节点的后继指向新插入节点
    p->next = pNew;
    //新插入节点的前驱指向尾节点
    pNew->prev = p;
    
    return 0;
}


//向双链表插入数据(中间插法)
int dlist_insert(NODE** head, DATA pos, DATA data)
{
    //创建新节点,申请空间
    NODE* pNew = (NODE*)malloc(sizeof(NODE));
    //判断是否申请成功
    if(!pNew)
    {
        return -1;
    }
    //给新节点赋初值
    pNew->data = data;
    pNew->next = pNew->prev = NULL;

    NODE* p = *head, *q = NULL;
    //第一种情况,没有头节点
    if(!p)
    {
        *head = pNew;
        return 0;
    }
    //第二种情况,只有一个头节点
    if(memcmp(&(p->data),&pos,sizeof(DATA)) == 0)
    {
        pNew->next = p;
        p->prev = pNew;
        *head = pNew;
        return 0;
    }
    //第三种情况,有多个节点
    while (p)
    {
        if(memcmp(&(p->data),&pos,sizeof(DATA)) == 0)
        {
            /*
            顺序:先自己再别人,先后再前
            */
            pNew->next = p;
            pNew->prev = q;
            p->prev = pNew;
            q->next = pNew;

            return 0;
        }
        q = p;
        p = p->next;
    }
    //第四种情况,找不到要插入的位置(也就是找不到pos)
    q->next = pNew;
    pNew->prev = q;
    
    return 0;
}


//链表数据查询
NODE* dlist_find(const NODE* head, DATA data)
{
    const NODE* p = head;

    while (p)
    {
        if (memcmp(&(p->data),&data,sizeof(DATA)) == 0)
        {
            return (NODE*)p;
        }
        //向后查询
        p = p->next;
        //向前查询
        //p = p->prev;
    }
    return NULL;
}


//链表数据更新
int dlist_update(const NODE* head, DATA old_data, DATA new_data)
{
    NODE* p = NULL;

    if( !(p = dlist_find(head,old_data)))
    {
        return -1;
    }
    p->data = new_data;
    return 0;
}


//链表数据遍历
void dlist_showAll(const NODE* head)
{
    const NODE* p = head;
    while(p)
    {
        //两种遍历方式不可同时使用
        printf("%d ", p->data);
        //向后遍历
        p = p->next;
        //向前遍历
        //p = p->prev;
    }
    printf("\n");
}


//链表数据删除
int dlist_delete(NODE** head, DATA data)
{
    NODE* p = *head;

    //第一种情况,原链表没有节点
    if(!p)
    {
        return -1;
    }
    //第二种情况,要删除的刚好是头节点
    if(memcmp(&(p->data),&data,sizeof(DATA)) == 0)
    {
        //1.链表中只有一个节点
        if(p->next == NULL)
        {
            free(p);
            *head = NULL;
            return 0;
        }
        //2.链表中有两个以上节点
        *head = p->next;        //此前head和p都指向头节点
        p->next->prev = NULL;   //解除p的引用关系

        free(p);

        return 0;
    }
    //第三种情况,逐个查找要删除节点
    while (p)
    {
        if(memcmp(&(p->data),&data,sizeof(DATA)) == 0)
        {
            p->prev->next = p->next;    //解除上一个节点和被删节点的关系
            //要删除的p刚好是尾节点
            if(p->next == NULL)
            {
                p->prev->next = NULL;
            }
            else    //要删除的p不是尾节点
            {
                p->next->prev = p->prev;
            }

            free(p);
            return 0;
        }
        p = p->next;    //改变循环条件
    }
    return -1;  
}


//销毁双链表
void dlist_destroy(NODE** head)
{
    NODE* p = *head, *q = NULL;

    while (p)
    {
        //实现指针尾随
        q = p;
        p = p->next;
        
        free(q);
    }
    *head = NULL;
}
----------------------------------------------------------------------------------------------
dlist_main.c
----------------------------------------------------------------------------------------------
#include "dlist.h"
#define OP   2  

int main(void)
{
    NODE*  head = NULL;

    int a[] = {1,3,5,7,9};
    int n = sizeof a / sizeof a[0];

    register int i = 0;
    for(; i < n; i++)
    {
        dlist_addTail(&head,a[i]);
    }
    dlist_showAll(head);

    while(1)
    {
#if (OP == 0)
        DATA   data;
        NODE   *pFind = NULL;
        printf("请输入要查找的数据(-1 退出):");
        scanf("%d",&data);
        if(data == -1)
           break;
        if(!(pFind = dlist_find(head,data)))
        {
            puts("查找的数据不存在,请重试...");
            continue;
        }  
        printf("在内存地址为 %p 的内存空间中找到了 %d\n",&(pFind->data),pFind->data);
#elif (OP == 1)   
        DATA   data;
        NODE   *pFind = NULL;
        printf("请输入要插入位置的数据(-1 退出):");
        scanf("%d",&data);
        if(data == -1)
           break;
        if(dlist_insert(&head,data,407))
        {
            puts("插入失败,请重试...");
            continue;
        }
        dlist_showAll(head);
#else
        DATA   data;
        NODE   *pFind = NULL;
        printf("请输入要删除位置的数据(-1 退出):");
        scanf("%d",&data);
        if(data == -1)
           break;
        if(dlist_delete(&head,data))
       {
            puts("删除失败,请重试...");
            continue;
       }
        dlist_showAll(head);
#endif
   }
     
    dlist_destroy(&head);
    puts("=====回收后====");
    dlist_showAll(head);
    
    return 0;
}
链表优缺点
  • 优点

    1. 插入删除数据仅需调整几个指针,较为便捷;

    2. 数据节点较多时,无需整片连续空间,可利用离散内存;

    3. 节点变化剧烈时,内存的分配和释放灵活、速度快;

  • 缺点

    1. 不支持立即随机访问任意随机数据;

    2. 需要多余指针记录节点间的关联关系;

相关推荐
鱼跃鹰飞17 分钟前
Leetcode面试经典150题-148.排序链表
算法·leetcode·链表·面试
Zion__25 分钟前
详解数据结构中的链表结构
数据结构·链表
熬夜学编程的小王27 分钟前
数据结构————栈的讲解(超详细!!!)
数据结构
黄卷青灯771 小时前
c语言 stdio.h 介绍
c语言·开发语言·stdio.h
安科瑞刘鸿鹏1 小时前
老旧电力系统安全隐患增加 该如何预防电气线路老化等因素引发的电气火灾呢?
运维·服务器·网络·数据结构·能源
Mephisto.java2 小时前
【数据结构与算法 | 灵神题单 | 合并链表篇】力扣2, 21, 445, 2816
数据结构·leetcode·链表
NameisBoy2 小时前
xmake vscode+clangd实现c/c++程序更精确跳转、补全
c语言·c++·vscode·clangd·xmake
Curry_Math2 小时前
数据结构:单链表
数据结构·c++
洛寒瑜3 小时前
【读书笔记-《30天自制操作系统》-18】Day19
c语言·开发语言·汇编·笔记·学习·操作系统·文件读取