线性结构是一种基本的数据结构,主要用于对具有单一前驱和后继的数据关系进行描述。它的特点是数据元素之间呈现一种线性关系,即是元素”一个接一个排列“。
一、线性表的定义
线性表是最简单、基本和常用的一种线性结构。
一个线性表是n个元素的有限序列,通常表示为(a1, a2, ... , an),非空线性表的特点如下:
- 存在唯一的一个”第一个“的元素。
- 存在唯一的一个”最后一个“的元素。
- 除第一个元素外,序列中的每个元素均只有一个直接前驱。
- 除最后一个元素外,序列中的每个元素均只有一个直接后继。
二、存储结构
(一)顺序存储
线性表的顺序存储是指用一组地址连续的存储单元依次存储线性表中的数据元素,从而使得逻辑上相邻的两个元素在物理位置上也相邻。
#define TRUE 1 #define FALSE 0 #define ERROR -1 #define NULL -2 #define MAXSIZE 100 typedef int bool; typedef int ElementType; typedef int Position; typedef struct LNode *List; List MakeEmpty(); Position Find(List L, ElementType X); bool Insert(List L, ElementType X, Position P); bool Delete(List L, Position P); /******************************************/ /*********** 线性表的顺序存储实现 ***********/ /******************************************/ struct LNode { ElementType Data[MAXSIZE]; Position Last; }; /* 初始化 */ List MakeEmpty() { List L; L = (List)malloc(sizeof(struct LNode)); L->Last = -1; return L; } /* 查找 */ Position Find( List L, ElementType X ) { Position i = 0; while( i <= L->Last && L->Data[i]!= X ) i++; if ( i > L->Last ) return ERROR; /* 如果没找到,返回错误信息 */ else return i; /* 找到后返回的是存储位置 */ } /* 插入 */ bool Insert( List L, ElementType X, Position P ) { /* 在L的指定位置P前插入一个新元素X */ Position i; if ( L->Last == MAXSIZE-1) { /* 表空间已满,不能插入 */ printf("表满"); return FALSE; } if ( P<0 || P>L->Last+1 ) { /* 检查插入位置的合法性 */ printf("位置不合法"); return TRUE; } for( i=L->Last; i>=P; i-- ) L->Data[i+1] = L->Data[i]; /* 将位置P及以后的元素顺序向后移动 */ L->Data[P] = X; /* 新元素插入 */ L->Last++; /* Last仍指向最后元素 */ return TRUE; } /* 删除 */ bool Delete(List L, Position P) { /* 从L中删除指定位置P的元素 */ Position i; if( P<0 || P>L->Last ) { /* 检查空表及删除位置的合法性 */ printf("位置%d不存在元素", P ); return FALSE; } for( i=P+1; i<=L->Last; i++ ) L->Data[i-1] = L->Data[i]; /* 将位置P+1及以后的元素顺序向前移动 */ L->Last--; /* Last仍指向最后元素 */ return TRUE; }
(二)链式存储
线性表的链式存储是通过指针链接起来的结点来存储数据元素,基本的结点结构如下所示:
数据域 | 指针域 |
存储各元素的结点地址并不要求是连续的,因此存储数据元素的同时必须存储元素之间的逻辑关系。同时,结点空间只有在需要的时候才申请,无须事先分配(顺序存储必须事先分配好空间大小)。
#define TRUE 1 #define FALSE 0 #define ERROR -1 #define NULL -2 #define MAXSIZE 100 typedef int bool; typedef int ElementType; typedef int Position; typedef struct LNode *List; List MakeEmpty(); Position Find(List L, ElementType X); bool Insert(List L, ElementType X, Position P); bool Delete(List L, Position P); /******************************************/ /*********** 线性表的链式存储实现 ***********/ /******************************************/ typedef struct LNode *PtrToLNode; struct LNode { ElementType Data; PtrToLNode Next; }; typedef PtrToLNode Position; typedef PtrToLNode List; /* 查找 */ Position Find( List L, ElementType X ) { Position p = L; /* p指向L的第1个结点 */ while ( p && p->Data!=X ) p = p->Next; /* 下列语句可以用 return p; 替换 */ if ( p ) return p; else return ERROR; } /* 带头结点的插入 */ bool Insert( List L, ElementType X, Position P ) { /* 这里默认L有头结点 */ Position tmp, pre; /* 查找P的前一个结点 */ for ( pre=L; pre&&pre->Next!=P; pre=pre->Next ) ; if ( pre==NULL ) { /* P所指的结点不在L中 */ printf("插入位置参数错误\n"); return false; } else { /* 找到了P的前一个结点pre */ /* 在P前插入新结点 */ tmp = (Position)malloc(sizeof(struct LNode)); /* 申请、填装结点 */ tmp->Data = X; tmp->Next = P; pre->Next = tmp; return true; } } /* 带头结点的删除 */ bool Delete( List L, Position P ) { /* 这里默认L有头结点 */ Position tmp, pre; /* 查找P的前一个结点 */ for ( pre=L; pre&&pre->Next!=P; pre=pre->Next ) ; if ( pre==NULL || P==NULL) { /* P所指的结点不在L中 */ printf("删除位置参数错误\n"); return false; } else { /* 找到了P的前一个结点pre */ /* 将P位置的结点删除 */ pre->Next = P->Next; free(P); return true; } }
根据结点的指针域的设置方式,还有其他的几种链表结构。
- 双向链表。每个结点包含两个指针:一个指向前驱,一个指向后继。其特点是从表中任意的结点出发,都可以从两个方向遍历链表。
- 循环链表。在单向链表(或双向链表)的基础上增加了表尾的指针指向链表的第一个结点,构成循环链表。其特点是从表中任意结点都可以遍历链表。
- 静态链表。借助数组来描述线性表的链式存储结构,用数组元素的下标表示元素所在结点的指针。