数据结构与算法01----线性表

线性表 [逻辑结构] 的顺序存储和链式存储 [存储结构(物理结构)]

在这里插入图片描述

Key 1:线性表是指各数据元素间保持“1对1”关系的数据结构
Key 2:线性表分为顺序表(数组)和链表,分别通过元素相邻和保存指针域的方式来实现“1对1”

线性表的基本操作:顺序表、(单) 链表

查找(定位)元素:按值查找

  • 给定长度为 n 的线性表,查找值为 v 的元素
  • (最坏)从头到尾遍历 => 时间复杂度O(n)
    在这里插入图片描述

顺序表新增 / 删除元素

  • 给定长度为 n 的顺序表,在指定位置 i 插入一个新元素

  • 给定长度为 n 的顺序表,删除位置 i 的元素
    在这里插入图片描述

  • 需要将位置 i 到位置 n - 1的所有元素都向后或向前挪一格

  • 最坏情况(i = 0)下,需要挪动全部的 n 个元素 => 时间复杂度为O(n)

  • 无需利用额外空间 => 空间复杂度为O(1)

(单)链表新增元素

  • 给定长度为 n 的顺序表,在第 i 个节点插入一个新元素在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

  • 首先需要从头结点开始逐个向后找 i - 1次 => 时间复杂度为O(n)

  • 找到后插入只需要修改第 i - 1个结点和待插入节点的 [后继结点地址] 即可 => O(1)

  • 无需利用额外空间 => 空间复杂度为O(1)

(单)链表删除元素

  • 给定长度为 n 的单链表,删除第 i 个结点在这里插入图片描述

  • 需要移动到第 i 个结点的前驱结点,最坏情况下移动 n - 1次 => 时间复杂度为O(n)

  • 修改前驱结点的后继指针 => O(1)

  • 无需利用额外空间 => 空间复杂度为O(1)

顺序表更新元素

  • 给定长度为 n 的顺序表,更新位置 i 的元素在这里插入图片描述

  • 无论 i 的值如何,都可以通过 i 直接访问位置 i 的元素,将其更新为 v’ => 时间复杂度为O(1) => 随机存取

(单)链表更新元素

  • 给定长度为 n 的 (单)链表,更新第 i 个结点的值
  • 需要从头结点开始按顺序找到第 i 个结点才能访问并更新它 => 顺序存取
  • 最坏情况遍历整个链表 => 时间复杂度为 O(n)

Key 1:顺序表中增加和删除一个元素将导致该位置后的元素前移或后移,复杂度为O(n)
Key 2:单链表增加和删除元素虽然不用移动元素,但需先找到其前驱结点,复杂度为O(n)
Key 3:若线性表需要频繁更新元素 -> 选择用顺序表实现(数组)
Key 4:若线性表需要频繁插入删除元素 -> 选择用链式表实现

线性表合并操作:集合表合并、有序表合并

线性表的集合式合并:只合并不同元素
在这里插入图片描述

  • 设A表长度为n,B表长度为m
  • 对于B表中的每个元素,都需要先判断其是否已经存在A里 => O(mn)
  • 如果存在,无需插入,如果不存在,将其插入在A的末尾 => O(1)
  • 总时间复杂度为 O(mn)
  • 空间复杂度:顺序表 O(m + n) 链表 O(1)

合并两个有序表:本来分别有序,合并结果仍然有序
在这里插入图片描述
合并两个有序顺序表
在这里插入图片描述
在这里插入图片描述

  • 设A表长度为n,B表长度为m
  • 先预留结果表空间:n + m个元素
  • 从表头开始同时逐个访问A表和B表元素,将当前位置上较小者放入结果表并后移一位
  • 总时间复杂度为 O(m + n)
  • 空间复杂度为 O(m + n)

合并两个有序单链表

在这里插入图片描述
注意:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
一直重复上图操作
在这里插入图片描述

  • 设A表长度为n,B表长度为m
  • 先创建一个头结点(哑结点dummy),其数据没有实际意义,只为用它的指针域
  • 从表头开始逐个同时遍历A和B,将当前已完成合并的表尾元素的后继结点设置为当前A和B游标中较小的一个,并将该游标向后移动一位
  • 时间复杂度为 O(m + n)
  • 空间复杂度为 O(1)

Key 1:合并两个有序表:逐一比较两表当前元素,将正确的元素添加进结果表并移动游标

代码实现(顺序表)

main.c

#include <stdio.h>
#include <stdlib.h>
#include "sqlist.c"

int main()
{
    sqlist list;
    sqlist_init(&list, 100);
    
    int i;
    //测试插入
    for (i = 1; i < 10; i++)
    {
        sqlist_insert(&list, 0, i);
        sqlist_print(&list);
    }

    sqlist_insert(&list, 9, 33);
    sqlist_print(&list);

    int r;

    r = sqlist_insert(&list, 11, 44);

    printf("invaild insert ret = %d\n", r);
    
    sqlist_print(&list);
    //size函数测试
    i = sqlist_size(&list);

    printf("list size = %d \n", i);
    //测试查找
    i = sqlist_search(&list, 5);

    printf("pos of 5 is %d \n", i);

    int v;
    //测试删除
    for (i = 0; i < 10; i++)
    {
        sqlist_delete(&list, 1, &v);
        
        printf("delete ret is %d \n", v);
        
        sqlist_print(&list);
    }
    //测试empty函数
    i = sqlist_empty(&list);

    printf("is empty? %d\n", i);
    //测试full函数
    i = sqlist_full(&list);

    printf("is full? %d\n", i);
    
    sqlist_destory(&list);

    return 0;
}

sqlist.h

#ifndef _SQLIST_H_
#define _SQLIST_H_

typedef struct sqlist
{
    int size; //空间大小
    int n; //当前数组中元素个数
    int* data; //数组 int data[100]
} sqlist;

int sqlist_init(sqlist* list, int size);
int sqlist_destory(sqlist* list);
//pos = 0插入到最前面 pos = 1插入到第一个元素后面
int sqlist_insert(sqlist* list, int pos, int value);
int sqlist_delete(sqlist* list, int pos, int* outv);
void sqlist_print(sqlist* list);
int sqlist_size(sqlist* list);
int sqlist_search(sqlist* list, int v);

int sqlist_empty(sqlist* list);
int sqlist_full(sqlist* list);

#endif

sqlist.c

#include "sqlist.h"
#include <stdio.h>

int sqlist_init(sqlist* list, int size)
{
    list->size = size;
    list->n = 0;
    list->data = (int*)malloc(size * sizeof(int));
    
    return list->data != NULL; //如果分配内存成功,返回1,否则返回0
}

int sqlist_destory(sqlist* list)
{
    //安全隐患,在堆溢出中有个很著名的溢出方法叫做double free
    if (list->data == NULL)
    {
        return 0;
    }

    free(list->data);
    list->data = NULL;

    return 1;
}

int sqlist_insert(sqlist* list, int pos, int value)
{
    int i;

    if (pos < 0 || pos > list->n)
    {
        return 0; //位置不合法
    }

    if (list->n == list->size)
    {
        return -1; //内存不够了。
    }
    //通过个案分析的方法确定循环的起始和结束!!!!!
    for (i = list->n; i > pos; i--)
    {
        list->data[i] = list->data[i - 1];
    }

    list->data[pos] = value; //一定要测试一下!!!!!
    list->n++;

    return 1; //插入成功
}

void sqlist_print(sqlist* list)
{
    int i;

    printf("sqlist: size = %d, n = %d [ ", list->size, list->n);

    for (i = 0; i < list->n; i++)
    {
        printf("%d ", list->data[i]);
    }

    printf("]\n");
}
//pos >= 1 <= n
int sqlist_delete(sqlist* list, int pos, int* outv)
{
    if (pos < 1 || pos > list->n)
    {
        return 0; //参数非法!!
    }

    if (list->n == 0)
    {
        return -1; //内容不够了
    }

    *outv = list->data[pos - 1];
    
    int i;
    //个案分析,着重分析循环的开始和结束
    //具体做法就是代入法。n = 3 pos = 1, date 0 = datee 1, date 1 = date 2
    for (i = pos; i < list->n; i++)
    {
        list->data[i - 1] = list->data[i];
    }

    list->n--;

    return 1; //删除成功
}

int sqlist_size(sqlist* list)
{
    return list->n;
}
//返回数组的下标还是逻辑下标?  返回逻辑下标
int sqlist_search(sqlist* list, int v)
{
    int i;

    for (i = 0; i < list->n; i++)
    {
        if (list->data[i] == v)
        {
            return i + 1;
        }
    }

    return 0; //找不到
}

int sqlist_empty(sqlist* list)
{
    return list->n == 0;
}

int sqlist_full(sqlist* list)
{
    return list->n == list->size;
}

代码实现(链表)

main.c

#include <stdio.h>
#include <stdlib.h>
#include "linkedlist.c"

int main()
{
    int i, x;

    node* head = linkedlist_init();
    //判空
    x = linkedlist_empty(head);

    printf("empty return %d\n", x);
    //测试插入
    for (i = 1; i < 9; i++)
    {
        linkedlist_insert(head, 0, i);
    }

    linkedlist_print(head);
    
    linkedlist_insert(head, 8, 99);
    linkedlist_print(head);
    
    linkedlist_insert(head, 18, 99);
    linkedlist_print(head);
    //测试删除
    printf("delete:");
    
    for (i = 0; i < 5; i++)
    {
        linkedlist_delete(head, 3, &x);
        
        printf("%d ", x);
    }

    printf("\n");
    
    linkedlist_print(head);
    //测试size
    printf("size = %d\n", linkedlist_size(head));
    //判空
    x = linkedlist_empty(head);

    printf("empty return %d\n", x);
    //测试搜索
    printf("pos of 99 is %d\n", linkedlist_search(head, 99));
    
    linkedlist_destory(head);

    return 0;
}

linkedlist.h

#ifndef _LINKED_LIST_H
#define _LINKED_LIST_H

typedef struct node
{
    int data; //数据
    struct node* next; //指向下一个节点的指针
} node;

node* linkedlist_init();
void linkedlist_destory(node* head);

int linkedlist_insert(node* head, int pos, int x);
//返回值为是否成功运行,1成功,0失败,删除的节点放在x中。
int linkedlist_delete(node* head, int pos, int* x);
void linkedlist_print(node* head);
int linkedlist_size(node* head);
int linkedlist_search(node* head, int x);

int linkedlist_empty(node* head);

#endif

linkedlist.c

#include "linkedlist.h"
#include <stdio.h>
//初始化 带有头结点的单链表
// h -> [ head ] -> [ first ] -> [ second ] -> [...] -> [ ]
node* linkedlist_init()
{
    node* p = (node*)malloc(sizeof(node));
    p->data = 0;
    p->next = NULL;

    return p; //这就是头结点,返回他
}

void linkedlist_destory(node* head)
{
    node* p;
    while (head != NULL)
    {
        p = head->next; //找到下一个节点
        free(head);
        head = p; // head指向原本的下一个节点
    }
}
// h -> [ head ] ->[1]->[2]->[3]->[4]
// 插入的位置,给0插入到1之前,给1插入到1之后
int linkedlist_insert(node* head, int pos, int x)
{
    int i;

    if (pos < 0)
        return 0;// 失败

    node* p = head;// p应该指向插入位置前一个
    for (i = 0; i < pos; i++)
    {
        if (p == NULL)
        {
            return 0;// 失败
        }

        p = p->next; // 向后移动一格
    }

    node* nn = (node*)malloc(sizeof(node));
    nn->next = p->next;
    nn->data = x;
    p->next = nn;

    return 1;// 成功
}

// h -> [ head ] ->[1]->[2]->[3]->[4]
// 假设pos从1开始编号
int linkedlist_delete(node* head, int pos, int* x)
{
    int i;

    node* p = head;
    // 验证循环次数对不对,就是通过代数的方法
    // 假设pos = 1,看他执行多少次,p最终指向了哪一个元素。
    for (i = 0; i < pos - 1; i++)
    {
        if (p == NULL)
        {
            return 0;// pos不合法,删除失败
        }

        p = p->next;// 向后一格
    }

    node* q = p->next;// 这就是要删除的节点
    *x = q->data;// 取出数据放入传出参数
    p->next = q->next;
    free(q);

    return 1;// 成功
}

void linkedlist_print(node* head)
{
    node* p = head->next;// 指向第一个有数据的节点
    while (p != NULL)
    {
        printf("%d ", p->data);
        
        p = p->next;
    }

    printf("\n");
}
// h -> [ head ] ->[1]->[2]->[3]->[4]
int linkedlist_size(node* head)
{
    node* p = head->next;

    int c = 0;

    while (p != NULL)
    {
        c++;
        p = p->next;
    }

    return c;
}
//根据值x获取位置,是一个从1开始编号的下标
int linkedlist_search(node* head, int x)
{
    node* p = head->next;

    int pos = 1;

    while (p != NULL)
    {
        if (p->data == x)
        {
            return pos;
        }

        p = p->next;
        pos++;
    }

    return 0; // 找不到返回0
}

int linkedlist_empty(node* head)
{
    return head->next == NULL;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Len1Mi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值