码迷,mamicode.com
首页 > 其他好文 > 详细

【数据结构】线性表

时间:2016-06-14 11:33:39      阅读:258      评论:0      收藏:0      [点我收藏+]

标签:

1. 线性表的定义

(1) 线性表的定义

  线性表(List):零个或多个数据元素的有限序列。

(2) 线性表的抽象数据类型

ADT 线性表(List)
Data
  线性表的数据对象集合为{a1,a2,...,an},每个元素的类型均为DataType。其中,除第一个元素a1外,每个元素有且仅有一个直接前驱元素,除最后一个元素an外,每一个元素有且只有一个后继元素。数据元素间的关系是一对一的关系。
Operation
  initList(&L):初始化操作,建立一个空的线性表L。
  listEmpty(L):若线性表为空,返回true,否则返回false。
  clearList(L):将线性表清空。
  getElem(L,i,&e):将线性表L中的第i个位置元素值返回给e。
  locateElem(L,e):在线性表L中查找与给定值e相等的元素,如果查找成功,返回该元素在表中序号表示成功;否则,返回0表示失败。
  listInsert(&L,i,e):在线性表L中的第i位置插入新元素e。
  listDelete(&L,i,&e):删除第i位置的元素,并用e返回其值。
  listLength(L):返回线性表L的元素个数。
endADT

2. 线性表的顺序表示及实现

(1) 顺序存储定义

  线性表的顺序存储结构,指的是用一段地址连续的存储单元依次存储线性表的数据元素。

(2) 线性表的顺序存储结构代码描述

1 #define LIST_INIT_SIZE 100    // 线性表存储空间的初始分配量
2 #define LISTINCREMENT 10    // 线性表存储空间的分配增量
3 
4 typedef struct
5 {
6     ElemType *elem;     // 存储空间基址
7     int length;         // 当前长度
8     int listsize;         // 当前分配的存储容量
9 }SqList;

(3) 地址计算方法

  LOC(ai+1) = LOC(ai) + c

  LOC(ai) = LOC(a1) + (i - 1) * c

(4) 顺序表的初始化

1 Status initSqList(SqList &L)
2 {
3     L.elem = (ElemType*) malloc (sizeof(ElemType) * LIST_INIT_SIZE); 
4     if (!L.elem) return ERROR;  // 存储空间分配失败
5     L.length = 0;  // 空表长度为0
6     L.listsize = LIST_INIT_SIZE;  // 初始存储容量
7     return OK; 
8 }

(5) 顺序表的插入操作

插入算法的思路:

  1. 如果插入位置不合理,抛出异常;
  2. 如果线性表长度大于等于数组长度,则动态增加容量或抛出异常;
  3. 从最后一个元素开始向前遍历到第i个位置,分别将它们都向后移动一个位置;
  4. 将要插入的元素填入位置i处;
  5. 表长加1。

参考代码如下:

技术分享
 1 Status insertSqList(SqList &L, int i, ElemType e)
 2 {
 3     if (i < 1 || i > L.length + 1) return ERROR; // i值不合法
 4     if (L.length == L.listsize)  // 当前存储空间已满
 5     {
 6         ElemType *newbase = (ElemType *) realloc(L.elem, (L.listsize + LISTINCREMENT) * sizeof(ElemType)); 
 7         if (!newbase) return ERROR;  // 存储分配失败
 8         L.elem = newbase;  // 新基址
 9         L.listsize += LISTINCREMENT;  // 增加存储容量 
10     }
11     ElemType *q = &(L.elem[i - 1]), *p = &(L.elem[L.length - 1]); // q为插入位置
12     for (p; p >= q; --p)  // 插入位置及之后的元素右移
13         *(p + 1) = *p;  
14     *q = e;  // 插入e
15     ++L.length;  // 表长加1
16     return OK; 
17 }
View Code

(6) 顺序表的删除操作

删除操作的算法思路:

  1. 如果删除位置不合理,抛出异常;
  2. 取出删除元素;
  3. 从删除元素位置开始遍历到最后一个元素位置,分别将它们都向前移动一个位置;
  4. 表长减1。

参考代码如下:

技术分享
 1 Status deleteSqList(SqList &L, int i, ElemType &e)
 2 {
 3     if (i < 1 || i > L.length) return ERROR;  // i值不合法
 4     ElemType *p = &(L.elem[i-1]), *q = &(L.elem[L.length-1]); // p为被删除元素的位置
 5     e = *p;  // 被删除元素的值赋给e
 6     for (p; p < q; ++p) // 被删元素之后的元素左移
 7         *p = *(p+1); 
 8     --L.length;  // 表长减1
 9     return OK;
10 }
View Code

(7) 线性表顺序存储结构的优缺点

优点:

  1. 无须为表示表中元素之间的逻辑关系而增加额外的存储空间;
  2. 可以快速地存取表中任一位置的元素。

缺点:

  1. 插入和删除操作需要移动大量元素;
  2. 当线性表长度变化较大时,难以确定存储空间的容量;
  3. 造成存储空间的"碎片"。

3. 线性表的链式表示及实现

(1) 单链表的定义

  为了表示每个数据元素ai与其直接后继数据元素ai+1之间的逻辑关系,对数据元素ai来说,除了存储其自身的信息之外,还需存储一个指示其直接后继的信息(即直接后继的存储位置)。我们把存储数据元素信息的域称为数据域,把存储直接后继位置的域称为指针域。指针域中存储的信息称做指针或链。这两部分信息组成数据元素ai的存储映像,称为结点(Node)。

  n个结点(ai的存储映像)链结成一个链表,即为线性表(a1,a2,...,an)的链式存储结构,因为此链表的每个结点中只包含一个指针域,所以叫做单链表

(2) 头指针与头结点

头指针:

  1. 头指针是指链表指向第一个结点的指针,若链表有头结点,则是指向头结点的指针;
  2. 头指针具有标识作用,所以常用头指针冠以链表的名字;
  3. 无论链表是否为空,头指针均不为空。头指针是链表的必要元素。

头结点:

  1. 头结点是为了操作的统一和方便而设立的,放在第一个元素的结点之前,其数据域一般无意义(也可存放链表的长度);
  2. 有了头结点,对在第一个元素结点前插入结点和删除第一个结点,其操作与其它结点的操作就统一了;
  3. 头结点不一定是链表必须要素。

(3) 线性表的链表存储结构代码描述

1 typedef struct LNode 
2 {
3     ElemType data; 
4     struct LNode *next; 
5 } LNode, *LinkList; 

(4) 单链表的读取

获得单链表第i个数据的算法思路:

  1. 声明一个结点p指向链表的第一个结点,初始化j从1开始;
  2. 当j<i时,就遍历链表,让p的指针向后移动,不断指向下一个结点,j累加1;
  3. 若到链表末尾p为空,则说明第i个结点不存在;
  4. 否则查找成功,返回结点p的数据。  

参考代码如下:

技术分享
 1 Status getElemLinkList(LinkList L, int i, ElemType &e)
 2 {
 3     int j = 1; 
 4     LinkList p = L->next; 
 5     while (p && j < i) // 寻找第i个结点
 6     {
 7         p = p->next; 
 8         ++j; 
 9     }
10     if (!p || j > i) return ERROR; //第i个元素不存在
11     e = p->data; 
12     return OK; 
13 }
View Code

(5) 单链表的插入与删除

单链表第i个数据插入结点的算法思路:

  1. 声明一个结点p指向链表的第一个结点,初始化j从1开始;
  2. 当j<i时,就遍历链表,让p的指针向后移动,不断指向下一个结点,j累加1;
  3. 若到链表末尾p为空,则说明第i个结点不存在;
  4. 否则查找成功,在系统中生成一个空结点s;
  5. 将数据元素赋值给s->data;
  6. 单链表的插入标准语句:s->next = p->next; p->next = s; 
  7. 返回成功。

参考代码如下:

技术分享
 1 Status insertLinkList(LinkList &L, int i, ElemType e)
 2 {
 3     int j = 1; 
 4     LinkList p = L; 
 5     while (p && j < i) // 寻找第i个结点
 6     {
 7         p = p->next; 
 8         ++j;
 9     }
10     if (!p || j > i) return ERROR;  // i值不合法
11     LinkList s = (LinkList) malloc (sizeof(LNode)); // 生成新结点 
12     s->data = e; 
13     s->next = p->next;
14     p->next = s; 
15     return OK; 
16 }
View Code

单链表第i个数据删除结点的算法思路:

  1. 声明一个结点p指向链表的第一个结点,初始化j从1开始;
  2. 当j<i时,就遍历链表,让p的指针向后移动,不断指向下一个结点,j累加1;
  3. 若到链表末尾p为空,则说明第i个结点不存在;
  4. 否则查找成功,将欲删除的结点p->next赋值给q;
  5. 单链表的删除标准语句:p->next = q->next; 
  6. 将q结点中的数据赋值给e,作为返回;
  7. 释放q结点;
  8. 返回成功。

参考代码如下:

技术分享
 1 Status deleteLinkList(LinkList &L, int i, ElemType &e)
 2 {
 3     int j = 1; 
 4     LinkList p = L; 
 5     while (p->next && j < i) // 遍历寻找第i个元素
 6     {
 7         p = p->next; 
 8         ++j; 
 9     }
10     if (!(p->next) || j > i) return ERROR; // i值不合法
11     LinkList q = p->next; 
12     p->next = q->next; 
13     e = q->data; // 将结点q中的数据赋给e
14     free(q); // 让系统回收此结点,释放内存
15     return OK; 
16 }
View Code

(6) 单链表的整表创建

单链表整表创建算法思路:

  1. 声明一结点p和计数器变量;
  2. 初始化一个空链表L;
  3. 让L的头指结点的指针指向NULL,即建立一个带头结点的单链表;
  4. 循环:
    1. 生成一新结点赋值给p;
    2. 随机生成一个数据赋值给p的数据域p->data;
    3. 将p插入到头结点与前一新结点之间。

实现算法的代码如下所示:

技术分享
 1 void createLinkList(LinkList &L, int n) // 头插法
 2 {
 3     L = (LinkList) malloc (sizeof(LNode)); 
 4     L->next = NULL; 
 5     for (int i = 0; i < n; ++i)
 6     {    
 7         LinkList p = (LinkList) malloc (sizeof(LNode)); // 生成新结点
 8         ElemType e; 
 9         scanf("%d", &e); // 输入元素值
10         p->data = e;  
11         p->next = L->next;  // 插入到表头
12         L->next = p; 
13     }
14 }
View Code

  这段代码里,我们其实用的是插队的办法,就是始终让新结点在第一的位置。这种算法简称为头插法。可事实上,我们还是可以不这样做,指新结点放都放到最后,这才是正常思维,这种算法称之为尾插法。实现代码如下所示:

技术分享
 1 void createLinkList(LinkList &L, int n) // 尾插法
 2 {
 3     L = (LinkList) malloc (sizeof(LNode)); 
 4     LinkList q = L;    
 5     for (int i = 0; i < n; ++i)
 6     {
 7         LinkList p = (LinkList) malloc (sizeof(LNode)); // 生成新结点
 8         ElemType e; 
 9         scanf("%d", &e); // 输入元素值
10         p->data = e; 
11         q->next = p;  // 插入到表尾
12         q = p; 
13     }
14     q->next = NULL;  // 表示当前链表结束
15 }
View Code

(7) 单链表的整表删除

单链表整表删除算法思路如下:

  1. 声明一个结点p和q;
  2. 将第一个结点赋值给p;
  3. 循环:
    1. 将下一结点赋值给q;
    2. 释放p;
    3. 将q赋值给p。

参考代码如下所示:

技术分享
 1 Status clearLinkList(LinkList &L)
 2 {
 3     LinkList p = L->next, q; // p指向第一个结点 
 4     while (p) // 没到表尾
 5     {
 6         q = p->next; 
 7         free(p); 
 8         p = q; 
 9     }
10     L->next = NULL; // 头结点指针域为空
11     return OK; 
12 }
View Code

(8) 单链表结构与顺序存储结构优缺点

存储分配方式:

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

时间性能:

  • 查找:
    • 顺序存储结构O(1);
    • 单链表O(n)。
  • 插入和删除:
    • 顺序存储结构需要平均移动表长一半的元素,时间复杂度O(n);
    • 单链表在查出某位置指针后,插入和删除时间仅为O(1)。

空间性能:

  • 顺序存储结构需要预分配存储空间,分大了,浪费,分小了易发生上溢;
  • 单链表不需要分配存储空间,只要有就可以分配,元素个数也不受限制。

 

【数据结构】线性表

标签:

原文地址:http://www.cnblogs.com/xiaoxxmu/p/5582374.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!