线性表

线性表


1 简介

1.1 定义

将具有“一对一”关系的数据“线性”地存储到物理空间中,这种存储结构就称为线性存储结构(简称线性表)。线性表要求数据类型必须一致。

1.2 存储结构

顺序存储结构:将数据依次存储在连续的整块物理空间中,简称顺序表。

链式存储结构:数据分散的存储在物理空间中,通过一根线保存着它们之间的逻辑关系,简称链表。

1.3 前驱和后继

数据结构中,一组数据中的每个个体被称为数据元素(简称元素)。某一元素的左侧相邻元素称为直接前驱,位于此元素左侧的所有元素都统称为前驱元素;某一元素的右侧相邻元素称为直接后继,位于此元素右侧的所有元素都统称为后继元素

2 顺序表

顺序表是线性表的一种,即将具有“一对一’”逻辑关系的数据按照次序连续存储到一整块物理空间上。

2.1 初始化

顺序表存储数据之前,除了要申请足够大小的物理空间之外,还要记录顺序表申请的存储容量以及顺序表的长度,也就是表中存储数据元素的个数。

2.2 插入元素

根据插入位置的不同,可分为3种情况:插入到顺序表的表头;在表的中间位置插入元素;尾随顺序表中已有元素,作为顺序表中的最后一个元素。这3种情况可用一种方法解决:将要插入位置元素及其后继元素向后移动一个位置,再将插入元素放到插入位置。

2.3 删除元素

只需找到目标元素,并将其后续所有元素整体前移 1 个位置,即可间接实现删除元素的目的。

/* 定义顺序表 */
#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) exit(0);
    t.length = 0;
    t.size = SIZE;
    return t;
}
/* 插入元素,data为插入的元素,index为插入的位置 */
table addData(table t, int data, int index) {
    if(index < 1 || index > t.length+1) return t;
    if(t.length == t.size) {
        t.head = (int *)realloc(t.head, (t.size+1) * sizeof(int));
        if(!head) return t;
        t.size += 1;
    }
    //将从插入位置开始的后续元素逐个后移
    for(int i = t.length-1; i >= index-1; i--) {
        t.head[i+1] = t.head[i];
    }
    t.head[index-1] = data;
    t.length++;
    return t;
}
/* 删除元素 */
table delData(table t, int index) {
    if(index > t.length || index < 1) return t;
    for(int i = index; i < t.length; i++)
        t.head[i-1] = t.head[i];
    t.length--;
    return t;
}
/* 查找元素 */
int findData(table t, int data) {
    for(int i = 0; i < t.length; i++) {
        if(t.head[i] == data) return i+1;
    }
    //查找失败返回-1
    return -1;
}

3 链表

链表是一种数据元素随机存储,并通过指针表示数据之间逻辑关系的存储结构,链表存储的数据元素,其物理存储位置是随机的。

3.1 节点

链表中每个数据的存储都由以下两部分组成:

数据域:数据元素本身。

指针域:指向直接后继元素的指针。

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

头节点:其实就是一个不存任何数据的空节点,通常作为链表的第一个节点。

首元节点:链表中称第一个存有数据的节点为首元节点。

3.2 初始化
3.3 插入元素

向链表中增添元素,根据添加位置不同,可分为以下 3 种情况:

  • 插入到链表的头部(头节点之后),作为首元节点。
  • 插入到链表中间的某个位置。
  • 插入到链表的最末端,作为链表中最后一个数据元素。

链表插入元素的思想是固定的,只需做以下两步操作:

  • 将新结点的 next 指针指向插入位置后的结点。

  • 将插入位置前结点的 next 指针指向插入结点。

typedef struct Node {
    int data;
    struct Node *next;
}node, *pNode;

node *initList()
{
    pNode list = (pNode)malloc(sizeof(node));
    if(!list)
    {
        printf("malloc error!\n");
        return NULL;
    }
    list->next = NULL;
    return list;
}

int IsExist(pNode list)
{
	return list != NULL;
}

int IsEmpty(pNode list)
{
	return list->next == NULL;
}
//根据数据查找结点
Node *findByData(int data, pNode list)
{
	pNode cur;
	cur = list->next;
	while(cur != NULL && cur->data != data)
		cur = cur->next;
	return cur;
}
//根据索引查找节点
Node *findByIndex(pNode list, int index)
{
    int i = 1;
    pNode cur = list->next;
    if(index <= 0 || index > getNodeNum(list)) return NULL;
    while(cur)
    {
        if(i == index) return cur;
        i++;
        cur = cur->next;
    }
    return NULL;
}

int InsertHead(pNode list, int data)
{
	if(!list)
    {
        printf("list not exist\n");
        return -1;
    }
    pNode tmp = (pNode)malloc(sizeof(node));
    if(!tmp)
    {
        printf("malloc error\n");
        return -1;
    }
    tmp->data = data;
    tmp->next = list->next;
    list->next = tmp;
    return 0;
}

int InsertTail(pNode list, int data)
{
    if(!list) return -1;
    pNode tmp = (pNode)malloc(sizeof(node));
    if(!tmp) return -1;
    tmp->data = data;
    tmp->next = NULL;
    
    pNode tail = list;
    while(tail->next != NULL)
        tail = tail->next;
    tail->next = tmp;
    return 0;
}

int showList(pNode list)
{
    if(!head) return -1;
    if(!head-next) return -1;
    pNode cur = head->next;
    while(cur)
    {
        printf("%d ", cur->data);
        cur = cur->next;
    }
    printf("\n");
    return 0;
}

int getNodeNum(pNode list)
{
    ...
    for(cur = head->next; !cur; cur = cur->next, count++)
        ;
    return count;
}

4 静态链表

静态链表,也是线性存储结构的一种,它兼顾了顺序表和链表的优点于一身,既能快速访问元素,又能快速增加或删除数据元素。

使用静态链表存储数据,数据全部存储在数组中(和顺序表一样),但存储位置是随机的,数据之间"一对一"的逻辑关系通过一个整形变量(称为"游标",和指针功能类似)维持(和链表类似)。

4.1 节点

静态链表存储数据元素至少需要包含以下2部分信息:

  • 数据域:用于存储数据元素的值。
  • 游标:其实就是数组下标,表示直接后继元素所在数组中的位置。
4.2 备用链表

备用链表:一条连接各个空闲位置的链表,作用是回收数组中未使用或之前使用过(目前未使用)的存储空间,留待后期使用。通常,备用链表的表头位于数组下标为 0(a[0]) 的位置,而数据链表的表头位于数组下标为 1(a[1])的位置。

备用链表摘除节点最简单的方法是摘除 a[0] 的直接后继节点,向备用链表中添加空闲节点也是添加作为 a[0] 新的直接后继节点。无需遍历备用链表,耗费的时间复杂度为 O(1)。

#define MAXSIZE 6
typedef struct {
    int data;
    int cur;
}component;
/* 创建备用链表 */
void reserveArr(component *array){
    for(int i = 0; i < MAXSIZE; i++)
        array[i].cur = i + 1;
    array[MAXSIZE-1].cur = 0;
}
/* 提取分配空间 */
int mallocArr(component *array){
    int i = array[0].cur;
    if(array[0].cur)
        array[0].cur = array[i].cur;
    return i;
}
/* 初始化静态链表 */
int initArr(component *array){
    reserveArr(array);
    int body = mallocArr(array);
    
}

5 循环链表

循环链表:把链表的两头连接,使其成为了一个环状链表,只需要将表中最后一个结点的指针指向头结点。

6 双向链表

6.1 定义

双向,指的是各节点之间的逻辑关系是双向的,但通常头指针只设置一个。双向链表中各节点包含以下 3 部分信息:

  • 指针域(prior):用于指向当前节点的直接前驱节点。

  • 数据域(data):用于存储数据元素。

  • 指针域(next):用于指向当前节点的直接后继节点。

6.2 添加节点

添加至表头

假设新元素节点为 temp,表头节点为 head,则需要做以下 2 步操作即可:

  • temp->next=head; head->prior=temp。
  • 将 head 移至 temp,重新指向新的表头。

添加至表中

  • 新节点先与其直接后继节点建立双层逻辑关系。

  • 新节点的直接前驱节点与之建立双层逻辑关系。

添加至表尾

  • 找到双链表中最后一个节点;

  • 让新节点与最后一个节点进行双层逻辑关系;

6.3 删除节点
6.4 查找节点
typedef struct node{
    struct node *prior;
    int data;
    struct node *next;
}node;
/* 初始化双向链表 */
node *initList(node *head){
    head = (node *)malloc(sizeof(node));
    head->prior = NULL;
    head->next = NULL;
    head->data = 1;
}
/* 添加节点 */
node *insertNode(node *head, int data, int index){
    node *tmp = (node *)malloc(sizeof(node));
    tmp->data = data;
    tmp->prior = NULL;
    tmp->next = NULL;
    //插入位置为链表头
    if(index = 1){
        tmp->next = head;
        head->prior = tmp;
        head = tmp;
    }else{
        node *body = head;
        for(int i = 1; i < index - 1; i++)
            body = body->next;
        //条件为真说明插入位置为链表尾
        if(!body->next){
            body->next = tmp;
            tmp->prior = body;
        }else{
            body->next->prior = tmp;
            tmp->next = body->next;
            body->next = tmp;
            tmp->prior = body;
        }
    }
    return head;
}
/* 删除节点 */
node *delNode(node *head, int data){
    node *tmp = head;
    while(tmp){
        if(tmp->data == data){
            tmp->prior->next = tmp->next;
            tmp->next->prior = tmp->prior;
            free(temp);
            return head;
        }
        tmp = tmp->next;
    }
    printf("链表中不存在该元素\n");
    return head;
}
/* 查找节点对应的索引 */
int findNode(node *head, int data){
    int index = 1;
    node *cur = head;
    while(cur){
        if(cur->data == data)
            return index;
        index++;
        cur = cur->next;
    }
    return -1;
}
  free(temp);
        return head;
    }
    tmp = tmp->next;
}
printf("链表中不存在该元素\n");
return head;

}
/* 查找节点对应的索引 */
int findNode(node *head, int data){
int index = 1;
node *cur = head;
while(cur){
if(cur->data == data)
return index;
index++;
cur = cur->next;
}
return -1;
}




  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值