大小: 28
备注:
|
大小: 10347
备注:
|
删除的内容标记成这样。 | 加入的内容标记成这样。 |
行号 1: | 行号 1: |
在这里详述 线性表. | [[TableOfContents]] == 线性表逻辑结构 == 线性表是一种典型的线性结构,是最简单且最常用的逻辑结构。 * 线性表是由n(n≥0)个数据元素(结点)a1,a2,…,an组成的有限序列。 * 数据元素的个数n定义为表的长度(n=0时称为空表)。 * 将非空的线性表(n>0)记作:(a1,a2,…,an) 对于非空的线性表: * 有且仅有一个开始结点a1,没有直接前趋; * 有且仅有一个终结结点an,没有直接后继; * 其余的内部结点ai(2≤i≤n-1)都有且仅有一个直接前趋ai-1和一个ai+1。 例子 * 学生成绩表中,每个学生及其成绩是一个数据元素,其中数据元素由学号、姓名、各科成绩及平均成绩等数据项组成。 * 英文字母表(A,B,…,Z)是线性表,表中每个字母是一个数据元素(结点) * 一副扑克牌也是一个线性表,其中数据元素是每张牌的花色和点数 == 线性表的基本运算 == * init(L):构造一个空的线性表L,即表的初始化。 * destroy(L):销毁线性表L * clear(L):将线性表L清空 * is_empty(L):判断线性表L是否为空表 * length(L):求线性表L中的元素个数,即求表长。 * get_data(L, i):取线性表L中的第i个结点,这里要求1 ≤ i ≤ length_list(L) * locate(L, x):在L中查找值为x 的结点,并返回该结点在L中的位置。若L中有多个结点的值和x 相同,则返回首次找到的结点位置;若L中没有结点的值为x ,则返回一个特殊值表示查找失败。 * insert(L,i,x):在线性表L的第i个位置上插入一个值为x 的新结点,使得原编号为i,i+1,…,n的结点变为编号为i+1,i+2,…,n+1的结点。这里1≤i≤n+1,而n是原表L的长度。插入后,表L的长度加1。 * remove(L,i):删除线性表L的第i个结点,使得原编号为i+1,i+2,…,n的结点变成编号为i,i+1,…,n-1的结点。这里1 ≤ i ≤ n,而n是原表L的长度。删除后表L的长度减1。 对于实际问题中涉及的其它更为复杂的运算,可以用基本运算的组合来实现。 == 线性表的顺序存储(顺序表 Sequential List) == === 顺序表的存储结构 === 采用顺序存储方式存储的线性表就称为顺序表。 即把线性表的结点按逻辑次序依次存放在一组地址连续的存储单元里的方法。 线性表中所有结点的类型相同,每个结点所占用存储空间大小亦相同。 假设表中每个结点占用len个存储单元,其中第一个单元的存储地址则是该结点的存储地址,并设表中开始结点a1的存储地址(简称为基地址)是loc(a1),那么结点ai的存储地址loc(ai)可通过下式计算:loc(a_i)=loc(a_1)+(i-1)*len,1 ≤ i ≤ n 在顺序表中,每个结点的存储地址是该结点在表中的位置i的线性函数。只要知道基地址和每个结点的大小,就可在相同时间内求出任一结点的存储地址,是一种可以随机访问的数据结构。 顺序表图示: 顺序表用固定长度的数组实现: {{{#!cplusplus #define MAXSIZE 100 typedef int elem_type; typedef struct{ elem_type elem[MAXSIZE]; int size; //表示数组中元素个数 }seq_list; }}} elem_type是为了描述统一而自定义的,实际使用中,可以定义为需要的类型。 由于C语言中数组的下标从0开始,所以线性表的开始结点a1在线性表中的序号为1,对应的数组下标为0,ai在线性表中的序号为i,对应的数组下标为i-1。 除了用数组存储线性表的元素外,顺序表还需要用一个变量来表示线性表的长度属性或者最后一个元素的位置,因此用结构类型来定义顺序表类型。 存放线性表结点的数组空间的大小MAXSIZE应仔细选值,使其既能满足表结点的数目动态增加的需求,又不致于预先定义过大而浪费存储空间。 === 初始化 === {{{#!cplusplus void init( seq_list * slt) { slt->size=0; } }}} === 遍历 === {{{#!cplusplus void print(seq_list *slt) { int i; for( i=0; i < slt->size; i++) printf("%5d", slt->elem[i]); } }}} === 判断是否为空 === {{{#!cplusplus int is_empty( seq_list *slt ) { return slt->size==0; } }}} === 查找值为x的结点位置 === {{{#!cplusplus int locate(seq_list *slt, elem_type x) { int i=0; while(i < slt->size && slt->elem[i]!=x) i++; return i<=slt->last? i+1:-1; } }}} === 取得第i个结点的值 === {{{#!cplusplus elem_type get_data(seq_list *slt, int position) { return slt->elem[position-1]; }}} === 任意位置插入 === {{{#!cplusplus void insert(seq_list *slt, int position, elem_type x) { int i; position--; for(i = slt->size; i > position; i--) slt->elem[i] = slt->elem[i-1]; slt->elem[position]=x; slt->size++; } }}} 插入算法时间性能分析 === 任意位置元素删除 === {{{ void remove(seq_list *slt, int position) { int i; position--; for(i=position; i<slt->size-1; i++) slt->elem[i]=slt->elem[i+1]; slt->size--; } }}} 删除算法时间效率 === 顺序表的用法举例 === {{{ int main() { seq_list sqlist; init_list(&sqlist); insert_list(&sqlist, 1, 1); print_list(&sqlist); printf("%d", locate_list(&sqlist, 10)); printf("%d", locate_list(&sqlist, 11)); while(!empty_list(&sqlist)) delete_list(&sqlist, sqlist.size); print_list(&sqlist); } }}} == 线性表的链式存储(带头结点的单链表) == === 链表的存储结构 === 数据结构的存储方式必须体现它的逻辑关系。在链式存储方式下,除存放一个结点的信息外,还需附设指针,用指针体现结点之间的逻辑关系。 如果一个结点有多个后继或多个前驱,那么可以附设相应个数的指针,一个结点附设的指针指向的是这个结点的某个前驱或后继。 线性结构的链式存储被称为链表。链表中可以只附设一个指针指向它的后继结点的存放位置;也可以设两个指针,一个指向前驱一个指向后继。 单链表是线性表链式存储的一种形式。其中的结点一般含有两个域,一个是存放数据信息的域,另一个是存放该结点的后继结点的地址的指针域。一个单链表必须有一个首指针指向单链表中的第一个结点。 带头节点的单链表存储结构:普通单链表的缺点:特殊情况太多,处理起来太麻烦 改进办法:增设头结点,头结点不存放任何数据 单链表存储结构实现 {{{#!cplusplus typedef int elem_type; struct node{ elem_type data; struct node *next; }; typedef struct node node; typedef struct node *link_list; }}} === 初始化 === {{{#!cplusplus void init(link_list *list) { *list = (link_list)malloc(sizeof(node)); (*list)->next = NULL; } }}} === 插入 === 基本插入算法 {{{#!cplusplus void insert(node *q, elem_type x) { p = (node*)malloc(sizeof(node)); p->data = x; p->next = q->next; q->next = p; } }}} 插入特定位置 {{{#!cplusplus void insert_pos(link_list node, int pos, elem_type x) { while(pos > 1) { node = node->next; pos--; } insert(node, x); } }}} === 删除 === 基本删除算法 {{{#!cplusplus void remove(node *pre) { node *p = pre->next; pre->next=p->next; free(p); } }}} 删除特定位置元素算法 {{{#!cplusplus void remove_pos(Node *node, int pos) { while(pos > 1) { node = node->next; pos--; } remove(head); } }}} 删除特定值的元素算法 {{{#!cplusplus void remove_x(node *head, elem_type x) { node *pre = head; while(pre->next!= NULL && pre->next->data != x) { pre=pre->next; } remove(pre); } }}} === 查找算法 === {{{#!cplusplus node *get_data(node *head, int pos) { while(pos > 0) { head = head->next; pos--; } return head; } }}} {{{#!cplusplus node *find_x(node *head, elem_type x) { head = head->next; while(head!=NULL && head->data != x) { head=head->next; } return head; } }}} === 思考 === 如何实现: * 打印链表的每一个元素的值 * 求链表的长度 * 合并两个有序的单链表,使他们依然有序 == 不带头结点的单链表 == 不设开始的头结点,head指针直接指向第一个元素。 插入、删除操作比较繁琐 == 循环单链表 == 如果希望从表中的任意一个结点开始,都能访问到表中的所有其它结点,可以设置表中最后一个结点的指针域指向表中的第一个结点,这种链表称为循环单链表(或单循环链表) === 循环单链表与单链表的不同 === * 单链表中某个结点p是表中最后一个结点的特征是p->next==NULL。 * 对于循环单链表,若首指针为head,表中的某个结点p是最后一个结点的特征应该是p->next==head。 * 判断单链表为空的条件是head->next==NULL * 判断循环单链表为空的条件是head->next == head === 循环单链表插入 === === 循环单链表的删除 === === 只设尾指针的循环单链表 === 在循环单链表中,常常只设一个尾指针不设头指针。 === 例子:合并两个循环单链表 === == 双链表 == === 双链表存储结构 === {{{#!cplusplus struct double_node{ elem_type data; double_node *next; }; typedef struct double_node double_node; typedef struct double_node *double_list; }}} === 双链表的插入 === === 双链表的删除 === == 练习 == 1. 设计一个算法,求顺序表中值为x的元素的个数。 1. 设计一个算法,将顺序表倒置。 1. 已知一个顺序表中的各结点的值是从小到大有序的,设计一个算法,插入一个值为x的结点,使顺序表中的结点仍然是从小到大有序的。 |
线性表逻辑结构
线性表是一种典型的线性结构,是最简单且最常用的逻辑结构。
- 线性表是由n(n≥0)个数据元素(结点)a1,a2,…,an组成的有限序列。
- 数据元素的个数n定义为表的长度(n=0时称为空表)。
- 将非空的线性表(n>0)记作:(a1,a2,…,an)
对于非空的线性表:
- 有且仅有一个开始结点a1,没有直接前趋;
- 有且仅有一个终结结点an,没有直接后继;
- 其余的内部结点ai(2≤i≤n-1)都有且仅有一个直接前趋ai-1和一个ai+1。
例子
- 学生成绩表中,每个学生及其成绩是一个数据元素,其中数据元素由学号、姓名、各科成绩及平均成绩等数据项组成。
- 英文字母表(A,B,…,Z)是线性表,表中每个字母是一个数据元素(结点)
- 一副扑克牌也是一个线性表,其中数据元素是每张牌的花色和点数
线性表的基本运算
- init(L):构造一个空的线性表L,即表的初始化。
- destroy(L):销毁线性表L
- clear(L):将线性表L清空
- is_empty(L):判断线性表L是否为空表
- length(L):求线性表L中的元素个数,即求表长。
- get_data(L, i):取线性表L中的第i个结点,这里要求1 ≤ i ≤ length_list(L)
- locate(L, x):在L中查找值为x 的结点,并返回该结点在L中的位置。若L中有多个结点的值和x 相同,则返回首次找到的结点位置;若L中没有结点的值为x ,则返回一个特殊值表示查找失败。
- insert(L,i,x):在线性表L的第i个位置上插入一个值为x 的新结点,使得原编号为i,i+1,…,n的结点变为编号为i+1,i+2,…,n+1的结点。这里1≤i≤n+1,而n是原表L的长度。插入后,表L的长度加1。
- remove(L,i):删除线性表L的第i个结点,使得原编号为i+1,i+2,…,n的结点变成编号为i,i+1,…,n-1的结点。这里1 ≤ i ≤ n,而n是原表L的长度。删除后表L的长度减1。
对于实际问题中涉及的其它更为复杂的运算,可以用基本运算的组合来实现。
线性表的顺序存储(顺序表 Sequential List)
1. 顺序表的存储结构
- 采用顺序存储方式存储的线性表就称为顺序表。 即把线性表的结点按逻辑次序依次存放在一组地址连续的存储单元里的方法。 线性表中所有结点的类型相同,每个结点所占用存储空间大小亦相同。 假设表中每个结点占用len个存储单元,其中第一个单元的存储地址则是该结点的存储地址,并设表中开始结点a1的存储地址(简称为基地址)是loc(a1),那么结点ai的存储地址loc(ai)可通过下式计算:loc(a_i)=loc(a_1)+(i-1)*len,1 ≤ i ≤ n 在顺序表中,每个结点的存储地址是该结点在表中的位置i的线性函数。只要知道基地址和每个结点的大小,就可在相同时间内求出任一结点的存储地址,是一种可以随机访问的数据结构。 顺序表图示: 顺序表用固定长度的数组实现:
- elem_type是为了描述统一而自定义的,实际使用中,可以定义为需要的类型。 由于C语言中数组的下标从0开始,所以线性表的开始结点a1在线性表中的序号为1,对应的数组下标为0,ai在线性表中的序号为i,对应的数组下标为i-1。 除了用数组存储线性表的元素外,顺序表还需要用一个变量来表示线性表的长度属性或者最后一个元素的位置,因此用结构类型来定义顺序表类型。 存放线性表结点的数组空间的大小MAXSIZE应仔细选值,使其既能满足表结点的数目动态增加的需求,又不致于预先定义过大而浪费存储空间。
2. 初始化
3. 遍历
4. 判断是否为空
5. 查找值为x的结点位置
6. 取得第i个结点的值
7. 任意位置插入
插入算法时间性能分析
8. 任意位置元素删除
void remove(seq_list *slt, int position) { int i; position--; for(i=position; i<slt->size-1; i++) slt->elem[i]=slt->elem[i+1]; slt->size--; }
删除算法时间效率
9. 顺序表的用法举例
int main() { seq_list sqlist; init_list(&sqlist); insert_list(&sqlist, 1, 1); print_list(&sqlist); printf("%d", locate_list(&sqlist, 10)); printf("%d", locate_list(&sqlist, 11)); while(!empty_list(&sqlist)) delete_list(&sqlist, sqlist.size); print_list(&sqlist); }
线性表的链式存储(带头结点的单链表)
1. 链表的存储结构
- 数据结构的存储方式必须体现它的逻辑关系。在链式存储方式下,除存放一个结点的信息外,还需附设指针,用指针体现结点之间的逻辑关系。 如果一个结点有多个后继或多个前驱,那么可以附设相应个数的指针,一个结点附设的指针指向的是这个结点的某个前驱或后继。 线性结构的链式存储被称为链表。链表中可以只附设一个指针指向它的后继结点的存放位置;也可以设两个指针,一个指向前驱一个指向后继。 单链表是线性表链式存储的一种形式。其中的结点一般含有两个域,一个是存放数据信息的域,另一个是存放该结点的后继结点的地址的指针域。一个单链表必须有一个首指针指向单链表中的第一个结点。 带头节点的单链表存储结构:普通单链表的缺点:特殊情况太多,处理起来太麻烦 改进办法:增设头结点,头结点不存放任何数据 单链表存储结构实现
2. 初始化
3. 插入
基本插入算法
插入特定位置
4. 删除
基本删除算法
删除特定位置元素算法
删除特定值的元素算法
5. 查找算法
6. 思考
如何实现:
- 打印链表的每一个元素的值
- 求链表的长度
- 合并两个有序的单链表,使他们依然有序
不带头结点的单链表
- 不设开始的头结点,head指针直接指向第一个元素。 插入、删除操作比较繁琐
循环单链表
- 如果希望从表中的任意一个结点开始,都能访问到表中的所有其它结点,可以设置表中最后一个结点的指针域指向表中的第一个结点,这种链表称为循环单链表(或单循环链表)
1. 循环单链表与单链表的不同
单链表中某个结点p是表中最后一个结点的特征是p->next==NULL。
对于循环单链表,若首指针为head,表中的某个结点p是最后一个结点的特征应该是p->next==head。
判断单链表为空的条件是head->next==NULL
判断循环单链表为空的条件是head->next == head
2. 循环单链表插入
3. 循环单链表的删除
4. 只设尾指针的循环单链表
在循环单链表中,常常只设一个尾指针不设头指针。
5. 例子:合并两个循环单链表
双链表
1. 双链表存储结构
2. 双链表的插入
3. 双链表的删除
练习
- 设计一个算法,求顺序表中值为x的元素的个数。
- 设计一个算法,将顺序表倒置。
- 已知一个顺序表中的各结点的值是从小到大有序的,设计一个算法,插入一个值为x的结点,使顺序表中的结点仍然是从小到大有序的。