数据结构 - 线性表

 

参考文档:

Linux 内核代码 include/list.h 源文件分析

 

线性表定义(List):

线性表的数据对象集合为{a1, a2, a3, a4, ...an}

除第一个元素a1外,每个元素都有一个直接前驱元素

除最后一个元素an外,每个元素都有一个直接后继

数据元素之间的关系是一 一对应的。

 

ADT

数据对象:D = {ai | ai 属于 ElemSet,  i = 1, 2, 3, ... n,  n>=0}

数据关系:除第一个元素a1外,每个元素都有直接前驱,除最后一个元素外,每个元素都有一个直接后继。

一般操作: 共13个操作,

初始化、

销毁、

清空、

判断是否为空、

获取值,

插入、

删除、

遍历、

查找、

逆转

 

// 初始化操作,建立一个空的线性表L

InitList(*L)   

 

// 初始条件:线性表已存在

// 操作结果: 销毁线性表

DestoryList(*L)  

 

// 初始条件: 线性表已存在

// 操作结果: 将线性表清空

ClearList(*L) 

 

// 初始条件: 线性表已存在

// 操作结果: 若线性表为空,返回true,否则返回false。

ListEmpty(L) 

 

// 初始条件: 线性表L已存在,  i >= 1 && i <= ListLength(i)

// 操作结果: 将线性表L中第i个元素的值返回给e.

GetElem(L, i, *e) 

 

// 初始条件: 线性表L已存在, compare()是数组判定

// 操作结果: 在线性表L中查找与给定e相等的元素,如果查找成功,返回该元素在表中的序列号,否则返回0表示失败。

LocateElem(L, e, compare()) 

 

// 初始条件: 线性表L已经存在

// 操作结果: 在L中第i个位置之前插入新的数据元素e, L的长度加1

ListInsert(*L, i, e) 

 

// 线性表L存在且非空, i >= 1 && i <= ListLength(L)

// 操作结果: 删除L的第i个元素,并用e返回其值, L的长度减1

ListDelete(*L, i, *e) 

 

// 初始条件: 线性表已经存在

// 操作结果: 返回L中数据元素的个数

ListLength(L)  返回线性表L的元素个数

 

// 初始条件: 线性表L已存在 

// 操作结果: 依次对L的每个元素调用函数visit(), 一旦visit()失败,则操作失败。

ListTraverse(*L, visit())

 

// 初始条件: 线性表L已经存在

// 操作结果: 若cur_e是L的数据元素,且不是第一个,则用pre_e返回它的前驱,否则操作失败,pre_e无定义

PriorElem(L, cur_e, &pre_e)

 

// 初始条件: 线性表已经存在

// 操作结果: 若cure_e是L的线性元素,且不是最后一个,则用next_e返回它的后继,否则操作失败,next_e无定义

NextElem(L, cur_e, &next_e)

 

 

线性表有两种实现方式

* 顺序表

* 链表

 

实现一:顺序表的表示

线性表的顺序存储结构定义三要素:

  • 存储空间的起始位置
  • 线性表的最大存储容量
  • 线性表当前长度
// sqlist.h
// ----- 线性表的动态分配顺序存储结构 ------
#define LIST_INIT_SIZE 10 /* 线性表存储空间的初始分配量 */
#define LISTINCREMENT 2 /* 线性表存储空间的分配增量 */

typedef int ElemType;

typedef struct
{
  ElemType *elem; /* 存储空间基址 */
  int length;     /* 当前长度 */
  int listsize;   /* 当前分配的存储容量(以sizeof(ElemType)为单位) */
} SqList;

 

顺序表操作实现

操作一:顺序表初始化操作函数

/*
 * 操作: 初始化一个顺序表
 * 前提条件: L 指向一个顺序表
 * 后置条件: 该顺序表被初始化
 * 返回值: 初始化成功返回1, 否则返回0
 */
Status InitList(SqList *L)
 {
   // (1)初始化分配内存
   (*L).elem=(ElemType * )malloc(LIST_INIT_SIZE * sizeof(ElemType));

   // (2)判断内存是否分配成功
   if(!(*L).elem)
     exit(OVERFLOW);

  // (3)将链表长度初始化为0
   (*L).length=0;  

  // (4)将链表容量初始为初始大小
   (*L).listsize=LIST_INIT_SIZE;
   
  // (5)成功返回
  return OK;
 }

 

操作二:销毁顺序表

 1 /*
 2  * 操作: 销毁顺序表
 3  * 前提条件: L 指向一个顺序表
 4  * 后置条件: 释放为顺序表分配的内存,顺序表置为0
 5  * 返回值: 释放成功返回1, 否则返回0
 6  */
 7 Status DestroyList_Sq(SqList *L)
 8 {
 9   // (1)释放线性表L中的内存
10    free((*L).elem);
11 
12   // (2)将线性表elem数据指向NULL
13    (*L).elem=NULL;
14 
15   // (3)将线性表长度初始化为0
16    (*L).length=0;
17 
18   // (4)将容量初始化为0
19    (*L).listsize=0;
20 
21   // (5)成功返回
22    return OK;
23 }

 

操作三:清空线性表

/*
 * 操作: 清空线性表
 * 前提条件: L 指向一个顺序表
 * 后置条件: 清空线性表中的值
 * 返回值: 释放成功返回1, 否则返回0
 */
Status ClearList(SqList *L)
{
  // (1)将线性表长度置为0 (
*L).length=0;

  // (2)成功返回
return OK; }

 

操作四:判断线性表是否为空

/*
 * 操作: 判断线性表是否为空
 * 前提条件: L 指向一个顺序表
 * 返回值: 为空表返回1, 否则返回0
 */
Status ListEmpty(SqList *L)
{
 // (1) 判断如果线性表长度为0,则线性表为空,否则为0
if(L.length==0) return TRUE; else return FALSE; }

 

操作五:获取线性表的长度

/*
 * 操作: 返回线性表长度
 * 前提条件: L 指向一个顺序表
 * 返回值: 返回线性表长度
 */
int ListLength(SqList *L)
{
 // (1) 返回线性表中长度值
return L.length; }

 

操作六:获取线性表L中第i个位置元素的值

/*
 * 操作: 获取线性表L中第i个位置元素的值
 * 前提条件: L 指向一个顺序表
 * 返回值: 返回L中第i个位置元素的值
 */
Status GetElem(SqList L,int i,ElemType *e)
{
 // (1)判断位置信息是否正确
if(i < 1 || i > L.length) exit(ERROR);
 // (2)将线性表中的值赋值给e
*e = *(L.elem+i-1);
 // (3)成功返回值
return OK; }

 

操作七:顺序线性表插入操作的实现

/*
 * 插入算法的思路:
 * 如果插入位置不合理,抛出异常
 * 如果线性表长度大于等于数据长度,抛出异常或动态增加容量
 * 从最后一个元素开始向前遍历到第i个位置,分别将它们都向后移动一个位置
 * 将要插入元素填入位置i处
 * 表长加1
 * 
 * 初始条件: 顺序线性表L已存在, 1<= i <= ListLength(L)
 * 操作结果: 在L中第i个位置之前插入新的数据元素e,L的长度加1
 */
 
 Status ListInsert(SqList *L, int i, ElemType e)
 {
     int k;
  
// (1)插入数据前判断线性表是否满 if(L->length == MAXSIZE)
return ERROR;
// (2)判断位置信息是否在线性表中
if(i<1 || i>L->length+1) return ERROR;

   // (3)如果插入的数据不是在表尾 if(i<=L->length) {
      // (3.1)从要插入的位置到表尾所有数据往向移动一位 for(k=L->length-1; k>=i-1; k--) L->data[k+1] = L->data[k]; }

//(4)将插入的数据e赋值给线性表中
   L->data[i-1] = e;

   //(5)线性表长度增1
   L->length++;

// 成功返回
   return OK; }

 

操作八:顺序表删除操作实现

/*
 * 线性表顺序存储结构删除元素
 * 如果删除位置不合理,抛出异常
 * 取出删除元素
 * 从删除元素位置开始遍历到最后一个元素位置,分别将它们都向前移动一个位置
 * 表长度减1
 *
 * 初始条件:顺序线性表L已存在, 1<= i <= ListLength(L)
 * 操作结果:删除L的第i个数据元素,并用e返回其值,L的长度减1
 */
 
 status ListDelete(SqList *L, int i, ElemType *e)
 {
     int k;
   // (1)判断线性表是否为空 if(L->length == 0) return ERROR;

// (2)判断位置是否正确 if(i<1 || i>L->length) return ERROR;

// (3)将要删除的元素赋值给e *e = L->data[i-1];

// (4)如果删除元素不是最后一个元素 if(i < L-length) {
// (4.1)将从位置后继元素向前移动 for(k=i; k<L-length; k++) L->data[k-1] = L-data[k]; }

   // (5)线表长度减1 L->length--;

// (6)成功返回 return OK; }

 

实现二:链式表示与实现

解决线性表顺序存储结构中移动和删除中需要大量移动元素的问题。

线性表链式存储结构的组成:

  • 数据域(存储数据元素)
  • 指针域(存储直接后继的位置)

 

链表可以分为

* 线性单向链表

* 循环链表

* 双向循环链表

 

线性链表的ADT

数据定义

// 定义节点类型
typedef struct LNode { ElemType data; struct LNode * next; } *Link, *Position;

// 定义链表类型
typedef struct {
Link head, tail; // 分别指向线性链表中第一个节点与最后一个节点
int len; // 指示线性表中数据元素的个数
}

 

操作

// 分配由p指向的值为e的结点

Status MakeNode(Link &p, ElemType e);

 

// 释放p所有结点

void FreeNode(Link &p);

 

// 构造一个空的线性链表L

Status InitList(LinkList &L);

 

// 销毁线性表L, L不再存在

Status DestroyList(LinkList &L);

 

// 将线性链表L重置为空表,并释放原链表的结点空间

Status ClearList(LinkList &L);

 

// 已知h指向线性链表的头结点,将s所指结点插入在第一个结点之前

Status InsFirst(Link h, Link s);

 

// 已知h指向线性链表的头结点,删除链表中的第一个结点并以q返回

Status DelFirst(Link h, Link &q);

 

// 将指针s所指(彼此以指针相连)的一串结点链接在线性链表L的最后一个结点之后,并改变链表L的尾指针指向新的尾结点

Status Append(LinkList &L, Link s);

 

// 删除线性表L中的尾结点并以q返回,改变链表L的尾指针指向新的尾结点

Status Remove(LinkList &L, Link &q);

 

// 已知p指向线性

Status InsBefore(LinkList &L, Link &p, Link s);

 

//

Status InsAfter(LinkList &L, Link &p, Link s);

 

Status SetCurElem(Link &p, ElemType e);

 

1. 线性表的单链表结构

/*线性表的单链表存储结构*/
typedef struct Node
{
    ElemType data;
    struct Node * next;
} Node;

/*定义单列表LinkList*/
typedef struct Node * LinkList; 

 

单链表的读取

/*
 * 单链表的读取GetElem()
 * 思路:获得链表第i个数据的
 * 声明一个结点p指向链表第一个结点,初始化j从1开始
 * 当j<i时,就遍历链表,让p的指针向后移动,不断指向下一结点,j累计加1
 * 若到链表末尾p为空,则说明第i个元素不存在
 * 否则查找成功,返回结点p的数据
 * 
 * 初始条件:线性表L已存在, 1 <= i <= ListLength(L)
 * 操作结果:用e返回L中第i个数据元素的值
 */

Status GetElem(LinkList L, int i, ElemType *e)
{
    int j = 1;         // j为计数器
    LinkList p;        // 声明一结点p
    p = L->next;       // 让p指向链表L的第一个结点
    
//(1)p不为空或者计数器j小于或没有等于i时,循环继续 while(p && j<i) { p = p->next; //让p指向下一个结点 ++j; }

// (2)判断元素i是否存在 if(!p || j>i) return ERROR; //第i个元素不存在 *e = p->data; //取第i个元素的数据 return OK; }

 

 

单链表的插入与删除

/*
 * 单链表将数据插入到第i个结点
 * 思路:
 * 声明一个结点p指向链表第一个结点,初始化j从1开始
 * 当j<i时,就遍历链表,让p的指针向后移动,不断指向下一结点,j累计加1
 * 若到链表末尾p为空,则说明第i个元素不存在
 * 否则查找成功,在系统中生成一个空结点s
 * 将数据元素e赋值给s->data
 * 单链表的插入语句: s->next = p->next; p->next = s;
 * 返回成功
 * 
 * 初始条件:线性表L已存在, 1 <= i <= ListLength(L)
 * 操作结果:在L中第i个位置之前插入新的数据元素e,L的长度加1
 */
 
Status ListInsert(LinkList *L, int i, ElemType e)
 {
     int j;              // (1)定义位置
     LinkList p, s;      // (3)定义p 与 s 两个链表
     p = *L;             // (4)将链表L拷贝一份到p
     j = 1;              // (2)将位置初始化1
     
// (5)链表非空 且 位置小于要插入的位置
while(p && j < i) { p = p->next; ++j; }
// (6)为要插入的节点分配内存空间
s = (LinkList)malloc(sizeof(Node));
// (7)将数据e赋值给新开的空间
s->data = e;

// (8)将新插入的链表指向下一个指针 s->next = p->next;

// (9)将s指向p p->next = s;

// (10)成功返回 return OK; }


/*
 * 单链表第i个数据删除结点的算法思路:
 * 声明一结点p指向链表第一个结点,初始化j从1开始;
 * 当j<i时,就遍历链表,让p的指针向后移动,不断指向下一个结点,j累加1
 * 若到链表末尾p为空,则说明第i个元素不存在;
 * 否则查找成功,将欲删除的结点p->next赋值给q;
 * 单链表的删除标准语句 p->next = q->next;
 * 将q结点中的数据赋值给e,作为返回;
 * 释放q结点
 * 返回成功
 *
 * 初始条件:顺序线性表L已存在, 1 <= i <= ListLength(L)
 * 操作结果:删除L的第i个数据元素,并用e返回其值,L的长度减1
 */

Status ListDelete(LinkList *L, int i, ElemType *e)
{
    int j = 1;
    LinkList p, q;
    p = *L;
    while (p->next && j < i)    /*遍历寻找第i个元素*/
    {
        p = p->next;
        ++j;
    }
    if(!(p->next) || j > i)
        return ERROR;            /*第i个元素不存在*/
    q = p->next;
    p->next = q->next;
    *e = q->date;                /*将q结点中的数据给e*/
    free(q);
    return OK;
}

 

单链表的创建的思想

/*
 * 单链表整表创建的算法思路:
 * 声明一个结点p和计数器变量i;
 * 初始化一个空链表L;
 * 让L的头结点指针指向NULL,即建立一个带头结点的单链表
 * 循环:
 *      生成一新结点赋值给p;
 *    随机生成一数字赋值给p的数据域p->data;
 *    将p播入到头结点与前一新结点之间
 *
 * 随机产生n个元素的值,建立带表头结点的单链线性表L(头插法)
 */

void CreateListHead(LinkList * L, int n)
{
    LinkList p;
    int i;
    srand(time(0));        //初始化随机数种子
    *L = (LinkList)malloc(sizeof(Node));
    (*L)->next = NULL;        //先建立一个带头结点的单链表
    for (i=0; i<n; i++)
    {
        p = (LinkList)malloc(sizeof(Node));
        p->data = rand()%100 + 1;        //随机生成100以内的数字
        p->next = (*L)->next;
        (*L)->next = p;
    } 
}

/* 随机产生n个元素的值,建立带表头结点的单链线性表L(尾插法)*/
void CreateListTail(LinkList *L, int n)
{
    LinkList p, r;
    int i;
    srand(time(0));
    *L = (LinkList)malloc(sizeof(Node));
    r = *L;
    for(i=0; i<n; i++)
    {
        p = (Node *)malloc(sizeof(Node));
        p->data = rand()%100 + 1;
        r->next = p;
        r = p;
    }
    r->next = NULL;        //表示当前链表结束
}

 

 

单链表的删除操作

/*
 * 单链表的整表删除思想:
 * 声明一结点p和q;
 * 将第一个结点赋值给p
 * 循环:
 *    将下一结点赋值给q;
 *    释放p
 *    将q赋值给p
 *
 * 初始条件:顺序线性表L已存在
 * 操作结果:将L重置为空表
 */

Status ClearList(LinkList * L)
{
    LinkList p, q;
    p = (*L)->next;
    while(p)
    {
        q = p->next;
        free(p);
        p = q;
    }
    (*L)->next = NULL;    //头结点指针为空
    return OK;
}

 

 

 

应用: 实现两个线性表并集操作

/* 实现两个线性表A和B的并集操作, A=A U B
 * 思想:循环B中的每个元素,判断当前元素是否在A中
 *         若不存在,则插入到A中即可。
 */

void union(List *La, List Lb)
{
    int La_len, Lb_len, i;
    ElemType e;                 //声明与La和Lb相同的数据元素e
    La_len = ListLength(La);    //得到线性表A的元素个数
    Lb_len = ListLength(Lb);    //得到线性表B的元素个数
    for(i = 1; i <= Lb_len; i++)
    {    
        GetElem(Lb, i, e);        //取线性表B中第i个数据元素赋值给e
        if(!LocateElem(La,e,equal))            //判断线性表A中不存在和e相同的元素
            ListInsert(La, ++La_len, e);    //将e插入到线性表A中最后一个位置上
    }
}

 

 

应用:

有两个线性表

LA = {3, 5, 8, 11}

LB = {2, 6, 8, 9, 11, 15, 20}

LC = {2, 3, 5, 6, 8, 8, 9,11, 11, 15, 20 }

 

void MergeList(List La, List Lb, List *Lc)
{
  InitList(Lc);
  i = j = 1; 
  k = 0;
  
  La_len = ListLength(La);
  Lb_len = ListLength(Lb);
  

}

 

 

线性表存储结构优缺点:

无须为表示表中元素之间的逻辑关系而增加额外的存储空间

可以快速地存取表中任一位置的元素

插入和删除操作需要移动大量元素

当线性表长度变化较大时,难以确定存储空间的容量

造成存储空间的碎片

 

3. 双向循环链表

 将单链表中终端结点的指针由空指针改为指向头结点,就使整个单链表形成一个环,这种头尾相接的单链表称为单循环链表,简称循环链表

/*线性表的双向存储结构*/
typedef struct DulNode
{
    ElemType data;
    struct DulNode * prior;    //直接前驱指针
    struct DulNode * next;    //直接后继指针
} DulNode, *DuLinkList;

 

双向链表的插入操作中

/*双向链表的插入*/
s->prior = p;
s->next = p->next;
p->next->prior = s;
p->next = s;

第一步:首先找到插入位置,节点 s 将插入到节点 p 之前 
第二步:将节点 s 的前驱指向节点 p 的前驱,即 s->prior = p->prior; 
第三步:将节点 p 的前驱的后继指向节点 s 即 p->prior->next = s; 
第四步:将节点 s 的后继指向节点 p 即 s->next = p; 
第五步:将节点 p 的前驱指向节点 s 即 p->prior = s;

 

/*双向链表删除*/
p->prior->next = p->next;
p->next-prior = p->prior;

第一步:找到即将被删除的节点 p 
第二步:将 p 的前驱的后继指向 p 的后继,即 p->prior->next = p->next; 
第三步:将 p 的后继的前驱指向 p 的前驱,即 p->next->prior = p->prior; 
第四步:删除节点 p 即 delete p; 

 

逆转链表

递归的方式
typedef struct _node
{
    int value;
    _node *next;
} node, *pnode;

node *reverse_list(node *h)
{
    if(h == NULL || h->next == NULL)
        return h;
    node *head = reverse_list(h->next);
    h->next->next = h;
    h->next = NULL;

    return head;
}


循环的方式
void reverse_list(node **head)
{
    node *p, *q, *r;

    p = *head;
    q = p->next;
    while(q!=NULL) 
    {
        //操作
        r = q->next;
        q->next = p;
        //切换
        p = q;
        q = r;
    }
    (*head)->next = NULL;
    *head = p;
}

 

 

链表排序

// 链表的排序
void sort_list(node *head)
{
    node *p, *q, *s;
    int t;
    p = head;
    while(p->next) 
    {
        s = p;
        q = p->next;
                //用q去遍历p后面待排序的结点,找到一个最小值和p进行交换
        while(q) 
        {
            if (q->value < s->value)
                s = q;
            q = q->next;
        }
        if (s != p) 
        {
            t = s->value;
            s->value = p->value;
            p->value = t;
        }
        p = p->next;
    }
}


// 以最高的效率删除结点
bool delete_node(node *&head, node *p)
{
    if(!p || !head)
        return false;
    if(p->m_pNext != NULL)   //不是尾指针
    {
        node *del = p->m_pNext;
        p->m_nValue = del->m_nValue;
        p->m_pNext = del->m_pNext;
        delete del;
        del = NULL;
    }
    else if(head == p) //是尾指针,同时只有一个结点
    {
        delete p;
        head = NULL;
    }
    else                     //是尾指针,同时有多个结点
    {
        node *pre = head;
        while(pre&&pre->m_pNext != p)
        {
            pre = pre->m_pNext;
        }
    if(pre==NULL)
        return false;
        delete p;
        p = NULL;
        pre->m_pNext = NULL;
    }
    return true;
}

 

判断单链表是否有环

low 走一步,fast 走两步, 相遇true, 否则false

bool check(const node* head)
{
    if(head==NULL) return false;
    node *low=head, *fast=head->next;
    while(fast!=NULL && fast->next!=NULL)
    {
      low=low->next;
      fast=fast->next->next;
      if(low==fast) return true;
    }
   return false;
}

 

posted @ 2016-06-27 22:49  elewei  阅读(511)  评论(0)    收藏  举报