笔记_408_数据结构_02. 线性表

02. 线性表

2.1 线性表的定义和基本操作

笔记_408_数据结构_02. 线性表_第1张图片

定义

  • 线性表是具有相同数据类型 n ( n ≥ 0 ) n(n≥0) n(n0)数据元素有限序列,其中 n n n 为表长,当 n = 0 n = 0 n=0 时线性表是一个空表。若用 L L L 命名线性表,则其一般表示为
    L = ( a 1 , a 2 , … , a i , a i + 1 , … , a n ) L = (a_1,a_2,…,a_i,a_{i+1},…,a_n) L=(a1,a2,,ai,ai+1,,an)

    • a i a_i ai 是线性表中的“第 i i i 个”元素线性表中的位序
      a 1 a_1 a1 是表头元素; a n a_n an 是表尾元素。
    • 除第一个元素外,每个元素有且仅有一个直接前驱;除最后一个元素外,每个元素有且仅有一个直接后继

2.2 顺序表

2.2_1 顺序表的定义

笔记_408_数据结构_02. 线性表_第2张图片

  • 顺序表:用顺序存储的方式实现线性表

  • 特点:

    • (最主要的特点)随机访问,即可以在 O ( 1 ) O(1) O(1) 时间内找到第 i i i 个元素。
    • 存储密度高,每个节点只存储数据元素
    • 拓展容量不方便(即便采用动态分配的方式实现,拓展长度的时间复杂度也比较高)
    • 插入、删除操作不方便,需要移动大量元素
  • 静态分配

    #define MaxSize 10 			//定义最大长度
    typedef struct{
    	ElemType data[MaxSize]; //用静态的“数组”存放数据元素
    	int length; 			//顺序表的当前长度
    }SqList; 					//顺序表的类型定义(静态分配方式)
    // 初始化
    void InitList(SqList &L){
        for(int i=0; i<MaxSize; i++)
            L.data[i]=0;
        L.length=0;
    }
    
  • 动态分配

    #define InitSize 10
    typedef struct{
        int *data;
        int MaxSize;
        int length;
    }SeqList;
    // 初始化
    void InitList(SeqList &L){
        L.data=(int *)malloc(InitSize*sizeof(int));
        L.length=0;
        L.MaxSize=InitSize;
    }
    // 动态增加长度
    void IncreaseSize(SeqList &L, int len){
        int *p=L.data;
        L.data= (int *)malloc((L.Maxsize+len)*sizeof(int));
        for(int i=0;i<L.length;i++)
            L.data[i]=p[i];
        L.MaxSize+=len;
        free(p);
    }
    

2.2.2_1 顺序表的增删查

笔记_408_数据结构_02. 线性表_第3张图片
笔记_408_数据结构_02. 线性表_第4张图片

#include

#define MaxSize 10 			//定义最大长度
typedef struct{
	int data[MaxSize]; //用静态的“数组”存放数据元素
	int length; 			//顺序表的当前长度
}SqList; 					//顺序表的类型定义(静态分配方式)
// 初始化
void ListInit(SqList &L){
    for(int i=0; i<MaxSize; i++)
        L.data[i] = 0;
    L.length = 0;
}
// 创建顺序表
void ListSet(SqList &L,int *arr,int len){
    L.length = len;
    for(int i=0;i<len;i++){
        L.data[i] = arr[i];
    }
}
// 输出
void ListDisplay(SqList &L){
    for(int i=0;i<L.length;i++)
        printf("%d ",L.data[i]);
    printf("\n");
}
// 插入:在L的位序i处插入元素e
// 时间复杂度:O(n)
bool ListInsert(SqList &L, int i, int e){
    if(i < 1||i > L.length+1)  //处理异常情况
        return false;
    if(L.length >= MaxSize)		//【易漏】处理异常情况
        return false;
    for(int j=L.length;j>=i;j--){
        L.data[j] = L.data[j-1];
    }
    L.data[i-1] = e;
    L.length++;
    return true;
}
// 删除:删除L中位序为i的元素e
// 时间复杂度:O(n)
bool ListDelete(SqList &L, int i,int &e){
    if(i < 1||i > L.length)  //处理异常情况
        return false;
    e = L.data[i-1];
    for(int j=i-1; j<L.length-1; j++){
        L.data[j]=L.data[j+1];
    }
    L.length--;
    return true;
}
// 按位查找
// 时间复杂度:O(1)
bool GetElem(SqList L,int i,int &e){
    if(i < 1||i > L.length)  //处理异常情况
        return false;
    e = L.data[i-1];
    return true;
}
// 按值查找:查找第一个元素值等于e的元素,位序赋值给i
// 时间复杂度:O(n)
bool SelectElem(SqList L,int &i, int e){
    for(int j=0;i<L.length;i++){
        if(L.data[i] == e){
            i = j;
            return true;
        }
    }
    return false;
}

2.3 链表

2.3_1 单链表的定义

笔记_408_数据结构_02. 线性表_第5张图片

  • 顺序表与单链表的对比

    • 顺序表

      • 优点:可随机存取,存储密度高
      • 缺点:要求大片连续空间,改变容量不方便
    • 单链表

      • 优点:不要求大片连续空间,改变容量方便
      • 缺点:不可随机存取,要耗费一定空间存放指针
// 定义单链表结类型
typedef struct LNode{
	ElemType data;			// 数据域
	struct LNode *next;		// 指针域
}LNode, *LinkList;

//等价于
typedef struct LNode LNode;
typedef struct LNode *LinkList;

LNode *L;   // 声明一个指向单链表第一个结点的指针
LinkList L; // 声明一个指向单链表第一个结点的指针

//增加一个新的结点:在内存中申请一个结点所需空间,并用指针 p 指向这个结点
struct LNode * p = (struct LNode *) malloc(sizeof(struct LNode));
  • 不带头结点的单链表
bool InitList(LinkList &L){
    L = NULL;
    return true;
}
  • 带头结点的单链表
bool InitList(LinkList &L){
    L = (LNode *)malloc(sizeof(LNode));
    if(L == NULL)
        return false;
    L->next = NULL;
    return true;
}

2.3_2 单链表的增删查 建立

笔记_408_数据结构_02. 线性表_第6张图片
笔记_408_数据结构_02. 线性表_第7张图片

查找
  • 带头结点 - 按位查找
LNode * GetElem(LinkList L,int i){
    if(i < 0)
        return NULL;
    LNode *p = L;
    int j = 0;
    while(p != NULL && j < i){
        p = p->next;
        j++;
    }
    return p;
}
  • 按值查找 - 带头结点
LNode * SelectElem(LinkList L,ElemType e){
    LNode *p = L->Next;
    while(p != NULL && p->data != e)
        p = p->next;
    return p;
}
  • 求表长 - 带头结点
int length(LinkList L){
    int len = 0;
    LNode *p = L;
    while(p->next != NULL){
        p = p->next;
        len++;
    }
    return len;
}
插入
  • 带头结点 - 按位序插入

笔记_408_数据结构_02. 线性表_第8张图片

// 在第 i 个位置上插入元素e
// 时间复杂度:O(n)
bool ListInsert(LinkList &L,int i,ElemType e){
    if(i < 1)		// 处理不合法数据
        return false;
    LNode *p;		// 指针p指向当前扫描到的结点
    int j = 0;		// 当前指针指向的是第几个结点
    p = L;			// L指向头结点
    // p = GetElem(L,i-1);
    while(p != NULL && j < i-1){   // 找到第i-1个结点
        p = p->next;
        j++;
    }
    if(p == NULL)		// i值不合法
        return false;
    LNode *s = (LNode *)malloc(sizeof(LNode));
    s->data = e;
    s->next = p->next;	//【注意】最后两句顺序不可颠倒
    p->next = s;
    return true;
}
  • 不带头结点 - 按位序插入
// 在第 i 个位置上插入元素e
// 时间复杂度:O(n)
bool ListInsert(LinkList &L,int i,ElemType e){
    if(i < 1)		// 处理不合法数据
        return false;
    if(i == 1){		// 【注意】需要额外处理插入第1个结点的情况
        LNode *s = (LNode *)malloc(sizeof(LNode));
        s->data = e;
        s->next = L;
        L = s;		// 头指针指向新结点
        return true;
    }
    LNode *p;		// 指针p指向当前扫描到的结点
    int j = 1;		// 当前指针指向的是第几个结点
    p = L;			// L指向头结点
    // p = GetElem(L,i-1);
    while(p != NULL && j < i-1){   // 找到第i-1个结点
        p = p->next;
        j++;
    }
    // InserNextNode(p,e);
    if(p == NULL)		// i值不合法
        return false;
    LNode *s = (LNode *)malloc(sizeof(LNode));
    s->data = e;
    s->next = p->next;	//【注意】最后两句顺序不可颠倒
    p->next = s;
    return true;
}
  • 指定结点的后插操作
// 后插操作:在p结点之后插入元素e
// 时间复杂度:O(1)
bool InsertNextNode(LNode *p,Elemtype e){
    if(p == NULL)
        return false;
    LNode *s = (LNode *)malloc(sizeof(LNode));
    if(s == NULL)		// 内存分配失败
        return false; 
    s->data = e;
    s->next = p->next;
    p->next = s;
    return true;
}
  • 指定结点的前插操作

笔记_408_数据结构_02. 线性表_第9张图片

// 前插操作:在p结点之前插入元素e
bool InserPriorNode(LNode *p,Elemtype e){
    if(p == NULL)
        return false;
    LNode *s = (LNode *)malloc(sizeof(LNode));
    if(s == NULL)
        reutrn false;
    s->next = p->next;	// 新结点s连接到p结点后
    p->next = s;
    s->data = p->data;	// p->data数据赋值给s
    p->data = e;		// e赋值给p->data
}
删除
  • 带头结点-按位序删除
// 删除第i个位置的元素,e返回删除的元素值
// 时间复杂度:O(n)
bool ListDelete(LinkList &L,int i,ElemType &e){
    if(i < 1)			// 处理不合法数据
        return false;
    LNode *p = L;
    int i=0;
    // GetElem(L,i-1);
    while(p != NULL && j < i-1){	// 找到第i-1个结点
        p = p->next;
        j++;
    }
    if(p == NULL)		// 值不合法
        return fasle;
    if(p->next == NULL)	// 第i-1个结点后已无其他结点
        return fasle;
    e = p->next->data;	
    p->next = p->next->next;
    return true;
}
  • 指定结点删除:需要修改其前驱结点的next指针

    • 法一:传入头指针,循环寻找p的前驱结点

    • 法二:偷天换日(把p下一个结点的数据域赋值给p的数据域,删除p的下一个结点)

      笔记_408_数据结构_02. 线性表_第10张图片

      ❗❗如果p是最后一个结点,则只能从头开始找到p的前驱

// 删除指定结点p
// 时间复杂度:O(1)
bool DeleteNode(LNode *p){
    if(p == NULL)
        return false;
    LNode *q = p->next;
    p->data = q->data;
    p->next = q->next;
    free(q);
    return true;
}
单链表的建立
尾插法
  • 带头结点
LinkList List_TailInsert(LinkList &L){
    int x;
    L = (LinkList)malloc(sizeof(LNode));
    LNode *s,*p = L;	// p指向当前已插入的最后一个结点
    scanf("%d",&x);
    while(x!=9999){
        s = (LNode *)malloc(sizeof(LNode));
        s->data = x;
        p->next = s;
        p = s;			// p指向最后一个结点
        scanf("%d",&x);
    }
    p->next = NULL;
    return L;
}
头插法
  • 带头结点
LinkList List_HeadInsert(LinkList &L){
    int x;
    L = (LinkList)malloc(sizeof(LNode));
    L->next = NULL;		// 【好习惯】只要是初始化单链表,就将头指针指向 NULL
    LNode *s;
    scanf("%d",&x);
    while(x!=9999){
        s = (LNode *)malloc(sizeof(LNode));
        s->data = x;
        s->next = L->next;
        L->next = s;
        scanf("%d",&x);
    }
    return L;
}
  • 不带头结点
void NoNode_HeadInsert(LinkList *L) {
    LNode *s;//要插入的节点
    int x;//要插入的元素
    scanf("%d", &x);
    while (x != 9999) {
        s = (LNode *)malloc(1, sizeof(LNode));
        s->data = x;
        s->next = NULL;
        if (NULL == *L) {
            *L = s;		//将新结点置位首节点也是尾结点
        } else {
            s->next = *L;
            *L = s;		//新结点置位尾结点
        }
        scanf("%d", &x);
    }
}
  • 【重要应用】头插法实现链表逆置
LinkList Reverse(LinkList &L){
	LNode *p,*q;
	p = L->next;			// p指向当前准备插入的结点
	L->next = NULL;			// 重新构造单链表
	while(p != NULL){
		q = p;
		p = p->next;
		q->next = L->next;	// 头插法 
		L->next = q;
	} 
	return L;
}

2.3_3 双链表

笔记_408_数据结构_02. 线性表_第11张图片
笔记_408_数据结构_02. 线性表_第12张图片

typedef struct DNode{
    ElemType data;
    struct DNode *prior, *next;
}DNode, *DLinkList;
// 初始化双链表
bool InitDLinkList(DlinkList){
    L = (DNode *)malloc(sizeof(DNode));
    if(L == NULL)
        return false;
    L->prior = NULL;
    L->next = NULL;
    return true;
}
  • 插入
// 在p结点之后插入s结点
bool InsertNextDNode(DNode *p,DNode *s){
    if(p == NULL || s == NULL)
        return false;
    s->next = p->next;
    if(p->next != NULL)
        p->next->prior = s;
    s->prior = p;
    p->next = s;
    return true;
}
  • 删除
// 删除p结点的后继结点
// 时间复杂度:O(1)
bool DeleteNextDNode(DNode *p){
    if(p == NULL )
        return false;
    DNode *q = p->next;     // q是p的后继结点
    if(q == NULL)           // p的后继结点存在
        return false;
    p->next = q->next;      // 改变p的后继结点
    if(q->next != NULL)     // 如果q的后继结点存在
        q->next->prior = p; // 改变q的后继结点的前驱结点
    free(q);
    return true;
}
// 销毁表
// 时间复杂度:O(1)
void DestoryList(DLinkList &L){
    while(L->next != NULL)
        DeleteNextDNode(L);
    free(L);    // 释放头结点
    L = NULL;   // 头指针指向NULL
}
  • 双链表的遍历

笔记_408_数据结构_02. 线性表_第13张图片

2.3_4 循环链表

笔记_408_数据结构_02. 线性表_第14张图片

循环单链表
typedef struct LNode{
    ElemType data;
    struct LNode *next;
}LNode, *LinkList;
// 初始化
bool InitList(LinkList &L){
    L = (LNode *)malloc(sizeof(LNode));
    if(L == NULL)
        return false;
    L->next = L;    // 头结点next指向头结点
    return true;
}
// 判空
bool Empty(LinkList L){
    if(L->next == L)	return true;
    else	return false;
}
// 判断结点p是否为循环单链表的表尾结点
bool isTail(LinkList L,LNode *p){
    if(p->next == L)	return true;
    else	return false;
}
  • 单链表:从一个结点出发只能找到后续的各个结点
  • 循环单链表:从一个结点出发可以找到其他任何一个结点
    • 从头结点找到尾部,时间复杂度为 O ( n ) O(n) O(n)
    • 从尾部找到头部,时间复杂度为O(1)
循环双链表
typedef struct DNode{
    ElemType data;
    struct DNode *prior, *next;
}DNode, *DLinkList;
// 初始化
bool InitDLinkList(DLinkList &L){
    L = (DNode *)malloc(sizeof(DNode));
    if(L == NULL)
        return false;
    L->prior = L;    // 头结点 prior 指向头结点
    L->next = L;     // 头结点的 next 指向头结点
    return true;
}
// 判空
bool Empty(DLinkList L){
    if(L->next == L)	return true;
    else	return false;
}
  • 插入删除
// 在p结点之后插入s结点
s->next = p->next;
p->next->prior = s;
p->next = s;
s->prior = p;

// 删除p的后继结点q
p->next = q->next;
q->next->prior = p;
free(q);

2.3_5 静态链表

笔记_408_数据结构_02. 线性表_第15张图片

2.3_6 顺序表和链表的比较

  • 问题:请描述顺序表和链表的bla bla bla…实现线性表时,用顺序表还是链表好?

    1. 顺序表和链表的逻辑结构都是线性结构,都属于线性表。
    2. 但是二者的存储结构不同,顺序表采用顺序存储…(特点,带来的优点缺点);链表采用链式存储…(特点、导致的优缺点)。
    3. 由于采用不同的存储方式实现,因此基本操作的实现效率也不同。当初始化时…;当插入一个数据元素时…;当删除一个数据元素时…;当查找一个数据元素时…
  • 顺序表 链表
    弹性(可扩容)
    增、删
  • 表长难以预估、经常要增加/删除元素——链表

  • 表长可预估,查询(搜索)操作较多——顺序表

你可能感兴趣的:(408_数据结构_notes,数据结构,笔记,考研)