【数据结构】线性表

113 阅读6分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第11天,点击查看活动详情 >>

线性表

  • 线性表有两种分别为 顺序表链表,在内存中,顺序表所占内存地址连续,链表所占内存地址不连续
  • 前驱元素:某一元素左侧的元素
  • 后继元素:某元素的右侧的元素

顺序表:

初始化一个新的顺序表

#include <stdio.h>
#include <stdlib.h>
#define Size 5
typedef struct Table{
    int * head;//声明了一个名为head的长度不确定的数组,也叫“动态数组”
    int length;
    int size;
}table;
table initTable(){
    table t;
    t.head=(int*)malloc(Size*sizeof(int));
    if (!t.head)
    {
        printf("初始化失败");
        exit(0);
    }
    t.length=0;
    t.size=Size;
    return t;
}
//输出顺序表中元素的函数
void displayTable(table t){
    for (int i=0;i<t.length;i++) {
        printf("%d ",t.head[i]);
    }
    printf("\n");
}
int main(){
    table t=initTable();
    //向顺序表中添加元素
    for (int i=1; i<=Size; i++) {
        t.head[i-1]=i;
        t.length++;
    }
    printf("顺序表中存储的元素分别是:\n");
    displayTable(t);
    return 0;
}

注意在初始化过程中,需要判断申请的内存地址是否申请成功,当不成功时创建会失败

typedef关键字代表此结构体可以重命名

插入

  • 若空间不足,需要重新申请空间
  • 使用realloc方法申请空间
table add(table t,int state,int num) {
        
    //当内存不够的时候   需要申请新的内存
    if (t.size <= t.length) {
        t.head = (int*)realloc(t.head,(t.size + 1) * sizeof(int));
        if (!t.head)
        {
            printf("新增初始化失败");
            return t;
        }
        t.size = t.size + 1;
    }

    displayTable(t);


    //后移元素
    //下标从0开始    长度从1开始
    for (int i = t.length-1; i >= state-1; i--)
    { 
        t.head[i + 1] = t.head[i];
    }
    t.head[state - 1] = num;
    t.length++;
    return t;

}

C的内存分配 realloc和malloc区别:

在C中有三个操作内存的函数,分别是

void* realloc(void* ptr, unsigned newsize);
void* malloc(unsigned size);
void* calloc(size_t numElements, size_t sizeOfElement);

它们的返回值都是请求系统分配的地址,如果请求失败就返回NULL

  • malloc用于申请一段新的地址,参数size为需要内存空间的长度,如: char* p;p=(char*)malloc(20);
  • calloc与malloc相似,参数sizeOfElement为申请地址的单位元素长度,numElements为元素个数,如:
    char* p;p=(char*)calloc(20,sizeof(char));
    这个例子与上一个效果相同
  • realloc是给一个已经分配了地址的指针重新分配空间,参数p为原有的空间地址,newsize是重新申请的地址长度如:
    char* p;
    p=(char*)malloc(sizeof(char)20);
    p=(char
    )realloc(p,sizeof(char)*40);
    注意,这里的空间长度都是以字节为单位。

malloc与calloc的区别为1块与n块的区别:\

  • malloc调用形式为(类型*)malloc(size):在内存的动态存储区中分配一块长度为“size”字节的连续区域,返回该区域的首地址。\
  • calloc调用形式为(类型*)calloc(n,size):在内存的动态存储区中分配n块长度为“size”字节的连续区域,返回首地址。\
  • realloc调用形式为(类型*)realloc(*ptr,size):将ptr内存大小增大到size。\
  • free的调用形式为free(void*ptr):释放ptr所指向的一块内存空间

删除

从顺序表中删除元素比较简单,当找到这个元素后 将后面元素向前移动,则会自动覆盖这个元素

查找

遍历数据表元素即可

链表

  • 链表中每个数据由两部分组成
    • 数据元素本身:数据域
    • 指向后继元素指针:指针域

节点结构体

链表中每个节点的具体实现,需要使用 C 语言中的结构体,具体实现代码为:

typedef struct Link{
    char elem; //代表数据域
    struct Link * next; //代表指针域,指向直接后继元素
}link; //link为节点名,每个节点都是一个 link 结构体
  1. 头指针:一个普通的指针,它的特点是永远指向链表第一个节点的位置。很明显,头指针用于指明链表的位置,便于后期找到链表并使用表中的数据;

  2. 节点:链表中的节点又细分为头节点、首元节点和其他节点:

    • 头节点:其实就是一个不存任何数据的空节点,通常作为链表的第一个节点。对于链表来说,头节点不是必须的,它的作用只是为了方便解决某些实际问题;
    • 首元节点:由于头节点(也就是空节点)的缘故,链表中称第一个存有数据的节点为首元节点。首元节点只是对链表中第一个存有数据节点的一个称谓,没有实际意义;
    • 其他节点:链表中其他的节点;

初始化链表

  • 头指针未分配内存
  • 头指针指向第一个节点
link * initLink(){
    link * p=NULL;//创建头指针
    link * temp = (link*)malloc(sizeof(link));//创建首元节点
    //首元节点先初始化
    temp->elem = 1;
    temp->next = NULL;
    p = temp;//头指针指向首元节点
    //从第二个节点开始创建
    for (int i=2; i<5; i++) {
     //创建一个新节点并初始化
        link *a=(link*)malloc(sizeof(link));
        a->elem=i;
        a->next=NULL;
        //将temp节点与新建立的a节点建立逻辑关系
        temp->next=a;
        //指针temp每次都指向新链表的最后一个节点,其实就是 a节点,这里写temp=a也对
        temp=temp->next;
    }
    //返回建立的节点,只返回头指针 p即可,通过头指针即可找到整个链表
    return p;
}
link * initLink(){
    link * p=(link*)malloc(sizeof(link));//创建一个头结点
    link * temp=p;//声明一个指针指向头结点,
    //生成链表
    for (int i=1; i<5; i++) {
        link *a=(link*)malloc(sizeof(link));
        a->elem=i;
        a->next=NULL;
        temp->next=a;
        temp=temp->next;
    }
    return p;
}

插入

  • 插入分为头部插入,尾部插入,中间插入
  • 三种插入方式步骤一样
    • 将新节点指向下一个节点地址
    • 将原节点地址指向新节点

注意:这两步不可以反过来!

link* add(link* p, int elem, int add) {
    link* temp = p;
    for (int i = 1; i < add-1; i++)
    {
        temp = temp->next;
        if(temp == NULL) {
            printf("违规");
            return 0;
        }
    }

    link* new = (link*)malloc(sizeof(link));
    new->elem = elem;
    new->next = temp->next;
    temp->next = new;
    return temp;

}
  • 包含头链表的新增在循环条件处不需要-1

删除

link* del(link* p,int del) {
    link* temp = p;
    link* n = NULL;
    for (int i = 1; i < del - 1; i++)
    {
        temp = temp->next;
        if (temp == NULL) {
            printf("违规");
            return 0;
        }
    }
    n = temp->next;
    temp->next =n->next ;
    free(n);
    return p;
}

顺序表与链表比较

顺序表链表
存储数据,需预先申请一整块足够大的存储空间,然后将数据按照次序逐一存储,数据之间紧密贴合,不留一丝空隙存储方式与顺序表截然相反,什么时候存储数据,什么时候才申请存储空间,数据之间的逻辑关系依靠每个数据元素携带的指针 维持
存储数据实行的是 "一次开辟,永久使用",即存储数据之前先开辟好足够的存储空间,空间一旦开辟后期无法改变大小(使用动 态数组的情况除外)。链表则不同,链表存储数据时一次只开辟存储一个节点的物理空间,如果后期需要还可以再申请。
空间利用率高空间利用率低
多访问,少插入删除少访问。多删除移动

单链表的反转

反转即单链表反转或逆置

方法1:迭代反转链表

此方法需要借助三个指针,遍历将三个指针逐个后移,直至mid指向最后一个,end指向空时

image.png

  • 首先改变1的指向,将其指向空
  • 将三个辅助指针向后移动一位
  • 再将2指向beg指针指向的地方
  • 循环往复

代码实现:

link* revers(link* head) {
    if (head == NULL || head->next == NULL) {
        return head;
    }
    else
    {
        link* beg = NULL;
        link* mid = head;
        link* end = head->next;

        while (1)
        {
            mid->next = beg;
            if (end == NULL) {
                break;
            }
            beg = mid;
            mid = end;
            end = end->next;
        }
        head = mid;
        return head;
    }
}

存储结构与存取结构

线性表的顺序存储结构是随机存取结构,而不是顺序存取结构;线性表的链式存储结构,又可以称为顺序存取结构,而不是 随机存取结构。

因为顺序表可以根据下标进行读取,但是链表不可以。