TableOfContents

线性表逻辑结构

线性表是一种典型的线性结构,是最简单且最常用的逻辑结构。

对于非空的线性表:

例子

线性表的基本运算

对于实际问题中涉及的其它更为复杂的运算,可以用基本运算的组合来实现。

线性表的顺序存储(顺序表 Sequential List)

1. 顺序表的存储结构

采用顺序存储方式存储的线性表就称为顺序表。即把线性表的结点按逻辑次序依次存放在一组地址连续的存储单元里的方法。线性表中所有结点的类型相同,每个结点所占用存储空间大小亦相同。

假设表中每个结点占用len个存储单元,其中第一个单元的存储地址则是该结点的存储地址,并设表中开始结点a1的存储地址(简称为基地址)是loc(a1),那么结点ai的存储地址loc(ai)可通过下式计算:loc(a_i)=loc(a_1)+(i-1)*len,1 ≤ i ≤ n

在顺序表中,每个结点的存储地址是该结点在表中的位置i的线性函数。只要知道基地址和每个结点的大小,就可在相同时间内求出任一结点的存储地址,是一种可以随机访问的数据结构。

顺序表图示:

顺序表用固定长度的数组实现:

   1 #define MAXSIZE 100
   2 typedef int elem_type;
   3 typedef struct{
   4     elem_type  elem[MAXSIZE];
   5     int size; //表示数组中元素个数
   6 }seq_list;

其中,elem_type是为了描述统一而自定义的,实际使用中,可以定义为需要的类型。

由于C语言中数组的下标从0开始,所以线性表的开始结点a1在线性表中的序号为1,对应的数组下标为0,ai在线性表中的序号为i,对应的数组下标为i-1。

除了用数组存储线性表的元素外,顺序表还需要用一个变量来表示线性表的长度属性或者最后一个元素的位置,因此用结构类型来定义顺序表类型。

存放线性表结点的数组空间的大小MAXSIZE应仔细选值,使其既能满足表结点的数目动态增加的需求,又不致于预先定义过大而浪费存储空间。

2. 初始化

   1 void init( seq_list * slt) {
   2     slt->size=0;
   3 }

3. 遍历

   1 void print(seq_list  *slt)
   2 {
   3    int i;
   4    for( i=0; i < slt->size; i++) 
   5         printf("%5d", slt->elem[i]);
   6 }

4. 判断是否为空

   1 int is_empty( seq_list  *slt ) {
   2      return  slt->size==0;
   3 }

5. 查找值为x的结点位置

   1 int find(seq_list *slt, elem_type x)
   2 {
   3     int i=0;
   4     while(i < slt->size && slt->elem[i]!=x) 
   5         i++;
   6     return i<=slt->last? i+1:-1;
   7 }

6. 取得第i个结点的值

   1 elem_type get_elem(seq_list *slt, int position) {
   2     return slt->elem[position-1];

7. 任意位置插入

   1 void insert(seq_list *slt, int position, elem_type x) { 
   2     int i;
   3     position--;
   4     for(i = slt->size; i > position; i--) 
   5         slt->elem[i] = slt->elem[i-1];
   6     slt->elem[position]=x;
   7     slt->size++;
   8 }

分析插入算法时间性能分析

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);
}

10. 顺序表可变长度的实现

   1 typedef int elem_type;
   2 typedef struct{
   3     elem_type *elem;
   4     int size; //表示数组中元素个数
   5     int capacity;
   6 }seq_list;

   1 #define INIT_SIZE 100
   2 void init( seq_list * slt) {
   3     slt->size=0;
   4     slt->capacity = INIT_SIZE;
   5     slt->elem = (elem_type *) malloc(sizeof(elem_type) * INIT_SIZE);    
   6 }

   1 void destroy( seq_list *slt) {
   2     free(slt-elem);
   3     slt->size = slt-capacity = 0;
   4 }

线性表的链式存储(带头结点的单链表)

1. 链表的存储结构

数据结构的存储方式必须体现它的逻辑关系。在链式存储方式下,除存放一个结点的信息外,还需附设指针,用指针体现结点之间的逻辑关系。如果一个结点有多个后继或多个前驱,那么可以附设相应个数的指针,一个结点附设的指针指向的是这个结点的某个前驱或后继。

线性结构的链式存储被称为链表。链表中可以只附设一个指针指向它的后继结点的存放位置;也可以设两个指针,一个指向前驱一个指向后继。

单链表是线性表链式存储的一种形式。其中的结点一般含有两个域,一个是存放数据信息的域,另一个是存放该结点的后继结点的地址的指针域。一个单链表必须有一个首指针指向单链表中的第一个结点。

单链表存储结构实现

   1 typedef int elem_type;
   2 struct node{
   3    elem_type data;
   4    struct node *next;
   5 };
   6 typedef struct node node;
   7 typedef struct node *link_list; 

普通单链表的缺点:特殊情况太多,处理起来太麻烦。改进办法:增设头结点,头结点不存放任何数据,称为带头节点的单链表。

2. 初始化

   1 void init(link_list *list) {
   2    *list = (link_list)malloc(sizeof(node));
   3    (*list)->next = NULL;
   4 }

3. 插入

基本插入算法

   1 void insert(node *q, elem_type x) 
   2 {
   3    p = (node*)malloc(sizeof(node));
   4    p->data = x;
   5    p->next = q->next;
   6    q->next = p;
   7 }

插入特定位置

   1 void insert_pos(link_list node, int pos, elem_type x) 
   2 {
   3    while(pos > 1) {
   4         node = node->next;
   5         pos--;
   6    }
   7    insert(node, x);
   8 }

4. 删除

基本删除算法

   1 void remove(node *pre) 
   2 {
   3    node *p = pre->next;
   4    pre->next=p->next;
   5    free(p);
   6 }

删除特定位置元素算法

   1 void remove_pos(Node *node, int pos) 
   2 {
   3    while(pos > 1) {
   4       node = node->next;
   5       pos--;
   6    }
   7    remove(head);
   8 }

删除特定值的元素算法

   1 void remove_x(node *head, elem_type x) 
   2 {
   3    node *pre = head;
   4    while(pre->next!= NULL && 
   5       pre->next->data != x) 
   6    {
   7       pre=pre->next;
   8    }
   9    remove(pre);
  10 }

5. 查找算法

   1 node *get_data(node *head, int pos) 
   2 {
   3    while(pos > 0) {
   4       head = head->next;
   5       pos--;
   6    }
   7    return head;
   8 }

   1 node *find_x(node *head, elem_type x) 
   2 {
   3    head = head->next;
   4    while(head!=NULL && head->data != x) 
   5    {
   6       head=head->next;
   7    }
   8    return head;
   9 }

6. 思考

如何实现:

不带头结点的单链表

循环单链表

1. 循环单链表与单链表的不同

2. 循环单链表插入

3. 循环单链表的删除

4. 只设尾指针的循环单链表

在循环单链表中,常常只设一个尾指针不设头指针。

5. 例子:合并两个循环单链表

双链表

1. 双链表存储结构

   1 struct double_node{
   2    elem_type data;
   3    double_node *next;   
   4 };
   5 typedef struct double_node double_node;
   6 typedef struct double_node *double_list;

2. 双链表的插入

3. 双链表的删除

练习

  1. 设计一个算法,求顺序表中值为x的元素的个数。
  2. 设计一个算法,将顺序表倒置。
  3. 已知一个顺序表中的各结点的值是从小到大有序的,设计一个算法,插入一个值为x的结点,使顺序表中的结点仍然是从小到大有序的。
ch3n2k.com | Copyright (c) 2004-2020 czk.