数据结构(一)--线性表

线性表

1.线性结构

线性结构是一种简单的数据结构,其特点包含以下四点:

  1. 存在唯一的一个“第一个”元素
  2. 存在位移的一个“最后一个”元素
  3. 除第一个元素外,其余元素均只有一个前驱
  4. 除最后一个元素外,其余元素均只有一个后驱

2.线性表

2.1线性表定义

线性表是线性结构的一种,也是最简单和最常用的一种数据结构。它是n个数据元素的有限序列。常见的线性表操作有:

  1. 初始化
  2. 销毁
  3. 清除
  4. 判空
  5. 插入
  6. 删除
  7. 合并

2.2 线性表的实现

线性表的实现分为两种:顺序表链表,两种方式各有所长,各有所短。在实际使用中可根据应用场景进行选择。

先将文本中用到的公有头文件贴上,主要是定义了一些状态宏。

//
// Created by 张兴锐 on 2018/7/25.
//

#ifndef DATASTRUCTURE_PUBLIC_H
#define DATASTRUCTURE_PUBLIC_H

// 函数结果状态代码
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define INFEASLIBLE -1
#define OVERFLOW

typedef int Status;
typedef int ElemType;

#endif //DATASTRUCTURE_PUBLIC_H

现在,先来介绍一下顺序表:

3.顺序表

顺序表指的是指数据之间逻辑地址连续&物理地址连续的线性表结构。如下图:

这里写图片描述

顺序表的优点在于随机可存取表中任意元素,读取时间在 O ( 1 ) ,不过缺点在于每次插入和删除都需要移动较多元素(下文中有时间复杂度的推导),同时由于不知道需要多大的存储空间,在开始往往需要较大的存储空间。

顺序表操作实现:

3.1 定义

//----------------线性表定义-----------
#define LIST_INIT_SIZE 100  //线性表初始化大小
#define LIST_INCREMENT 10   //线性表存储空间每次分配增量
typedef struct{
    ElemType *elem;     //线性表的基地址(首元素地址)
    int length;         //当前长度
    int listsize;       //当前分配的容量
}Sqlist;

3.2初始化

Status initList_Sq(Sqlist &L){
    //构造一个空的线性表
    L.elem = (ElemType *)malloc(LIST_INIT_SIZE*sizeof(ElemType));
    if(!L.elem) exit(ERROR) ;
    L.length = 0;
    L.listsize = LIST_INIT_SIZE;
    return OK;
}

3.3插入

在顺序表的第i个位置前插入元素,将第i个元素到最后一个元素整体向后移动。

/**
 * 在顺序表的第i个位置前插入元素
 * @param L 将要进行操作的线性表
 * @param i 在第i个位置之前进行插入
 * @param e 插入的值
 * @return 插入是否成功:
 *                      OK 成功
 *                      ERROR 失败
 */
Status ListInsert_Sq(Sqlist &L,int i,ElemType e){
    //i的合法位置 1<=i<=L.length + 1
    if(i < 1 || i> L.length + 1) exit(ERROR); //插入位置不合法
    if(L.length >= L.listsize){ //空间已满,增加空间
        ElemType *newbase = (ElemType *)realloc
                (L.elem,(L.listsize+LIST_INCREMENT)*sizeof(ElemType));
        if(!newbase) exit(ERROR);  //分配地址失败
        L.elem = newbase;           //新基地址
        L.listsize+=LIST_INCREMENT; //增加容量
    }
    ElemType *q = &L.elem[i-1]; //q为插入位置
    //从第length个元素到第i各元素进行搬移
    for(ElemType *p = &L.elem[L.length-1];p>= q;p--){
        *(p+1) = *p;    //向后搬移
    }
    *q = e;
    L.length++;
    return OK;
}

3.4 删除

在顺序表L中删除第i个元素,以e作为返回值,将第i+1到最后一个元素整体前移。

/**
 * 在顺序表L中删除第i个元素,以e作为返回值
 * @param L
 * @param i
 * @param e
 * @return OK
 *          ERROR
 */
Status ListDelete_Sq(Sqlist &L,int i,ElemType &e){
    if(i< 1 || i>L.length) exit(ERROR);//删除位置错误
    ElemType *q = &L.elem[i-1];     //将要删除的位置地址
    e = *q;
    ElemType *p = L.elem + L.length -1; //表尾地址
    for(;q<p;q++){
        *q = *(q+1);
    }
    L.length--;
    return OK;
}

3.5 合并

将线性表a和线性表b合并到a中,只合并b中有而a中没有的元素。

int LocateElem(Sqlist &l,int e,bool (*compare)(ElemType,ElemType)){
    int i = 1;
    ElemType *q = l.elem;
    while(i <= l.length && !(*compare)(*q++,e)) i++;
    if(i<= l.length) return i;
    else return 0;
}

bool equal(ElemType a,ElemType b){
    if(a == b){
        return TRUE;
    }
    return FALSE;
}
/**
 * 将线性表a和线性表b合并到a中,只合并b中有而a中没有的元素
 * @param a
 * @param b
 */
void Merge1(Sqlist &a,Sqlist b){

    for(int i = 0;i<b.length;i++){
        int e = b.elem[i];
        if(!LocateElem(a,e,equal)){
            ListInsert_Sq(a,a.length+1,e);
        }
    }
}

3.6 插入与删除的时间复杂度

假设 p i 是在第i个元素之前插入一个元素的概率,则在长度为 n 的线性表中插入一个元素时所需要移动元素次数的期望为:

E i s = i = 1 n + 1 p i ( n i + 1 )

p i = 1 n + 1 ,则
E i s = 1 n + 1 i = 1 n + 1 ( n i + 1 ) = n 2

假设 q i 是删除第 i 个元素的概率,则在长度为 n 的线性表中删除一个元素时所需移动元素次数的期望为:
E d l = i = 1 n q i ( n i )

q i = 1 n ,则
E d l = 1 n i = 1 n ( n i ) = n 1 2

4.链表

链式结构不要求逻辑上相邻的元素在物理位置上也相领,因此他没有顺序存储结构所有的弱点(插入删除的移动性),但同时也失去了顺序表可随机存取的优点。链表由数据域指针域构成。其结构如下:

这里写图片描述

4.1 定义

//----------------链表定义------------
typedef struct LNode{
  ElemType data;
  struct LNode *next;
}*LinkList;

4.2 插入

链表在第i个位置前插入元素e。

/**
 * 链表在第i个位置前插入元素e
 * @param head
 * @param i
 * @param e
 * @return
 */
Status LinkListInsert(LinkList &head,int i,ElemType e){
    LinkList p = head;
    int j = 0;
    while(p && j< i-1) {
        p = p->next;
        j++;
    }
    if(!p || j> i-1){
        exit(ERROR);
    }
    LinkList v = (LinkList)malloc(sizeof(LNode));
    v->data = e;
    v->next = p->next;
    p->next = v;
    head->data++;
    return OK;
}

过程如下:
这里写图片描述

4.3 删除

删除链表第i个元素,并以e作为返回

/**
 * 删除链表第i个元素,并以e作为返回
 * @param head
 * @param i
 * @param e
 * @return
 */
Status LinkListDelete(LinkList &head,int i,ElemType &e){
    LinkList p = head;
    int j = 0;
    while(p && j<i-1){ //找到第i-1个元素
        p = p->next;
        j++;
    }
    if(!p || j>i-1){
        exit(ERROR);
    }
    LinkList q = p->next;
    e = q->data;
    p->next = q->next;
    free(q);        //释放空间
    head->data--;
    return OK;
}

其过程如下:

这里写图片描述

4.4 创建链表

/**
 * 创建链表
 * @param head 头结点,数据域存储整个链表的长度
 * @return  OK
 *          ERROR
 * @input 输入-1作为链表结束
 */
Status LinkListCreate(LinkList &head){
    head = (LinkList)malloc(sizeof(LNode));
    head->data = 0;
    head->next = NULL;
    ElemType e;
    cin>>e;
    while(e != -1){
       /* LinkList p = (LinkList)malloc(sizeof(LNode));
        p->data = e;
        p->next = NULL;
        head->next = p;
        head->data++;*/
       LinkListInsert(head,head->data+1,e);
       cin>>e;
    }
    return OK;

}

4.5 合并链表

将有序链表a,b合并为链表c(这里的有序假设为不递减)

/**
 * 将有序链表a,b合并为链表c(这里的有序假设为不递减)
 * @param a
 * @param b
 * @param c
 */
void MergeList(LinkList &a,LinkList &b,LinkList &c){
    LinkList p_a = a->next;
    LinkList p_b = b->next;
    c = a; //初始设定a为头结点
    c->data = a->data+b->data;
    LinkList p_c = c;
    free(b);
    b = NULL;
    while(p_a && p_b){
        if(p_a->data <= p_b->data){
            p_c->next = p_a;
            p_c = p_a;//p_c = p_c->next;
            p_a = p_a->next;
        }else{
            p_c->next = p_b;
            p_c = p_b;//p_c = p_c->next;
            p_b= p_b->next;
        }
    }
    p_c->next = p_a?p_a:p_b;
}

5. Others’链表

  1. 将尾指针再指向头指针,则变为了循环链表
  2. 在定义结构中加入前驱指针,可变为双向链表
    这两种方式都是在单链表上的拓展,实现并不复杂,这里就不贴出具体代码了。

6.总结

总体来说,线性表的两种方式并不复杂,写代码的时候可以在草稿纸上画上一个线性表样,跟着写就好了。

猜你喜欢

转载自blog.csdn.net/qq_35109096/article/details/81212359