2、数据结构与算法 - 线性表

大纲:

1、线性表-关于顺序存储的实现(增删改查)
2、线性表-关于链式(单链表)存储的设计(增删改查与头插法/尾插法)


线性表

1、顺序表

 对于非空的线性表和线性结构,其特点如下:
 存在唯一的一个被称作“第一个”的数据元素;
 存在唯一的一个被称作“最后一个”的数据元素;
 除了第一个之外,结构中的每个数据元素均有一个前驱
 除了最后一个之外,结构中的每个数据元素都有一个后续。

 

链表结构与顺序存储结构优缺点对比

 1、存储分配方式

  • 顺序存储结构用一段连续的存储单元一次存储线性表的数据元素;
  • 单链表采用链式存储结构,用一组任意的存储单元放线性表的元素;

 2、时间性能
    查找

  • 顺序存储 O(1)
  • 单链表  O(n)

    插入和删除

  • 存储结构插入需要在插入位置后面的向后移动空出插入位置,删除需要删除后后面的向前移动(平均需要移动一个表长一半的元素),时间 O(n)
  • 单链表查找某位置后的指针后,插入和删除为 O(1)

3、空间性能

  • 顺序存储结构需要预先分配存储空间,分太大的话浪费空间,分太小的话容易发生溢出;
  • 单链表不需要预先分配存储空间,只要有就可以分配使用,元素个数也不受限制;

 

线性表常见设计方式

线性表 ->顺序存储(逻辑相邻,物理存储地址相邻)(a[1] 与 a[2]相邻)

 ADT list{
     Data:线性表的数据对象集合为{a1,a2,a,......an},每个元素的类型均为DataType,其中,除了第一个元素a1 外,每一个元素有且只有一个直接前驱元素,除了最后一个元素an 外,每个元素有且只有一个直接后续元素,数据元素之间的关系是一对一的关系。
 
OPeration(操作)
     InitList(&L)           初始化表
     操作结果:初始化操作,建立一个空的线性表L
 
     DestroyList(&L)        销毁表
     初始条件:线性表L已存在
     操作结果:销毁线性表L
 
     ClearList(&L)          清空表
     初始条件:线性表L已存在
     操作结果:将L重置为空表
 
     ListEmpty(L)           表是否为空
     初始条件:线性表L已存在
     操作结果:若L为空表,则返回true,否则返回false
 
     ListLength(L)          表长度(元素个数)
     初始条件:线性表L已存在
     操作结果:返回L中数据元素的个数
 
     ......
     GetElem(L,i,&e)        获取元素
     初始条件: 线性表L已存在,且1<=i<ListLength(L)
     操作结果: 用e返回L中第i个数据元素的值;
 
     LocateElem(L,e)
     初始条件: 线性表L已存在
     操作结果: 返回L中第1个值与e相同的元素在L中的位置. 若数据不不存在则返回0;
 
     PriorElem(L, cur_e,&pre_e);
     初始条件: 线性表L已存在
     操作结果: 若cur_e是L的数据元素,且不不是第⼀一个,则⽤用pre_e返回其前驱,否则操作失败.
 
     NextElem(L, cur_e,&next_e);
     初始条件: 线性表L已存在
     操作结果: 若cur_e是L的数据元素,且不不是最后⼀一个,则⽤用next_e返回其后继,否则操作失败.
 
     ......
 
     ListInsert(L,i,e);
     初始条件: 线性表L已存在,且1<=i<=listLength(L)
     操作结果: 在L中第i个位置之前插⼊入新的数据元素e,L⻓长度加1.
 
     ListDelete(L,i);
     初始条件: 线性表L已存在,且1<=i<=listLength(L)
     操作结果: 删除L的第i个元素,L的⻓长度减1.
 
     TraverseList(L);
     初始条件: 线性表L已存在
     操作结果: 对线性表L进⾏行行遍历,在遍历的过程中对L的每个结点访问1次.
 }ADT List.


单链表

结点

结点:数据域 和 指针域
 单向链表的不能通过一个结点找到前一个结点(双向链表和循环链表可以)
 线性表的最大特点是不连续的,每个数据与数据通过指针域进行连接。
 
 
 单链表逻辑状态

设计单链表的时候可以加一个头结点
头结点 在首元结点(单链表中第一个带有值的节点)之前。头结点的好处是不需要对头结点做特殊处理
初始化的时候就要加入头结点
头结点的主要作用是做增删改查的
函数执行完毕的,时候函数内的临时变量等都会被销毁 

 

引申:

xcode 创建一个C语言工程

单链表操作

#include <stdio.h>
#include "string.h"
#include "ctype.h"
#include "stdlib.h"
#include "math.h"
#include "time.h"

#define MAXSIZE 20
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0

typedef int ElemType;//ElemType 类型根据 实际情况而定,这里假设为int
typedef int Status;//Status 是很熟的类型,其值是函数结果状态代码,如OK等

//设计一个结构体
typedef struct Node {
    ElemType data;//数据域  不需要指针,因为这是一个指针
    struct Node *next;//指针域  指向整个结构体的指针 -> Node
}Node;

//重定义一下 以免每次定义都很麻烦
typedef struct Node * LinkList;

1、初始化

//1、初始化
Status InitList(LinkList *L){
    *L = (LinkList)malloc(sizeof(Node));
//    存储空间分配失败
    if (*L == NULL) return ERROR;
    (*L)->next = NULL;//头结点的next 指向首元结点。初始化的时候next 还是空
//    头结点不是插入进去的,是初始化的时候就带入进入的
    
    return OK;
}
int main(int argc, const char * argv[]) {
    // insert code here...
    printf("Hello, World!\n");
    
    //1、初始化
    //L1  和  L2 是等价的,L2是前面重定义了
    LinkList L1,L,L3;
    struct Node * L2;
    Status iStatus;
    ElemType e;
    
//    初始化,但是一般不会这么初始化。这里用来演示
//    L1 = (LinkList)malloc(sizeof(Node));
//    L2 = (LinkList)malloc(sizeof(Node));
//    L1->data = 1;
//    L2->data = 2;

//    1、单链表初始化
    iStatus = InitList(&L);
    printf("L 是否初始化成功?(0:失败,1:成功) %d\n",iStatus);

2、链表的遍历

//2、单链表的遍历
Status ListTraverse(LinkList L){
//    遍历的时候应该从首元结点开始,L现在是指向头结点,所以p = L->next;
    LinkList p = L->next;
    while (p) {
        printf("%d  ",p->data);
        p=p->next;
    }
    printf("\n");
    
    return OK;
}
ListTraverse(L);

3、单链表插入

思路:
 例如在 a b 两个结点中间插入 c
 1、查找需要插入的前面一个结点                a
 2、创建一个新的结点,让S 指向这个新的结点      c
 3、给新创建的结点赋值                      c
 4、将新创建的结点 指针域指向插入位置的结点     c->b
 5、将插入前一个结点指向新创建的结点           a->c
 必须先4 再5,因为如果先把 a指向c,那么b就会丢失在链表中,所以必须先把c 指向b,再去把a 指向c。
 

//3、单链表插入
//首先写一个循环
Status ListInsert(LinkList *L,int i,ElemType e){
    
    int j;//用来计数的
    LinkList p,s;//p 用来遍历,s 创建新结点
    p = *L;//p 指向*L
    j = 1;//计数,0位置是头结点,从1开始。
    
//    p为空 就没必要向下找了,同时
    while (p && j<i) {
        p = p->next;//找到前一个结点()
        ++j;
    }
    if (!p || j>i) return ERROR;
    
//    1、创建新的结点
    s =(LinkList)malloc(sizeof(Node));
    s->data = e;
    
//    2、插入,将前面的结点指向显得结点
    s->next = p->next;
    p->next = s;
    
    return OK;
}
//    2、单链表插入 数据
    for(int j = 1;j<=10;j++)
    {
        iStatus = ListInsert(&L, 1, j);
    }
    printf("L 插入后\n");
    ListTraverse(L);

//L 插入后
//10  9  8  7  6  5  4  3  2  1

4、单链表删除


 例如在 a b c 三个结点删除中间 b结点
 1、找到待删除前一个结点                   a
 2、q 指向待删除的结点                    q->b
 3、将前面结点的next 指向 删除后面的结点     a->c
 4、将需要删除的结点 free
 必须要用q 指向b,因为修改后如果不指向就找不到b 了。因为b 必须要被free 将内存空间空出来给内存池,交给别人用

//4、单链表删除
//初始条件:顺序线性表L 已存在,1<=ListLength(L)
//操作结果:删除L 的第i 个数据元素,并用e 返回其值,L的长度减1
Status ListDelete(LinkList *L,int i,ElemType *e){
    int j;
    LinkList p,q;
    p = (*L)->next;
    j =1;
    
//    查找第i-1 个结点,p指向该结点
    while (p->next && j<(i-1)) {
        p = p->next;
        ++j;
    }
//    当i>n 或者 i<1 时,删除位置不合理
    if (!(p->next) || j>i-1) return ERROR;
    
//    q指向要删除的结点
    q = p->next;
//    将q 的后续赋值给p 的后续
    p->next = q->next;
//    将q结点中的数据给e
    *e = q->data;
//    让系统回收此结点,释放内存;
    free(q);
    
    return OK;
}
//    3、删除第5个元素
    iStatus = ListDelete(&L, 5, &e);
    printf("删除第5个元素为: %d\n",e);
    ListTraverse(L);

//删除第5个元素为: 6
//10  9  8  7  5  4  3  2  1  

 5、头插法(前插法)
 每次都从线性表的首元结点之前插入数据。
 

//5、头插法(前插法)
void CreateListHead(LinkList *L,int n){
    LinkList p;
//    建立一个带头结点单链表
    *L = (LinkList)malloc(sizeof(Node));
    (*L)->next = NULL;
    
    for (int i=0; i<n; i++) {
        p=(LinkList)malloc(sizeof(Node));
        p->data = i;//i 是演示数据
        p->next = (*L)->next;//将插入的结点指针指向原来的首元结点(也就是头结点的指向 *L->next)
        (*L)->next = p;//将 *L指针指向新的结点 p
    }
}
//    4、头插法
    CreateListHead(&L1, 20);
    printf("头插法创建的L1的元素:\n");
    ListTraverse(L1);

//头插法创建的L1的元素:
//19  18  17  16  15  14  13  12  11  10  9  8  7  6  5  4  3  2  1  0

 6、尾插法(后插法)
 每次都从尾结点插入结点
 

//6、尾插法(后插法)
//创建两个指针,p指向新创建需要插入的结点  r指向尾结点
void CreataListTail(LinkList *L,int n){
    LinkList p,r;
    
    //    建立一个带头结点单链表
    *L = (LinkList)malloc(sizeof(Node));
    
    r = *L;
    for (int i=0; i<n; i++) {
        p = (LinkList)malloc(sizeof(Node));
        p->data = i;//演示数据
        
        r->next = p;//
        r = p;
    }
    r->next = NULL;
}
//    4、尾插法
    CreataListTail(&L1, 20);
    printf("尾插法创建的L1的元素:\n");
    ListTraverse(L1);

//尾插法创建的L1的元素:
//0  1  2  3  4  5  6  7  8  9  10  11  12  13  14  15  16  17  18  19  

 

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值