TableOfContents

线性表Linear List

1. 线性表的定义

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

线性表是由n(n≥0)个数据元素(结点)a1,a2,…,an组成的有限序列。

数据元素的个数n定义为表的长度(n=0时称为空表)。

将非空的线性表(n>0)记作:(a1,a2,…,an)

数据元素ai(1≤i≤n)只是个抽象符号,其具体含义在不同情况下可以不同。

2. 线性表的逻辑结构特征

对于非空的线性表:

3. 例子

学生成绩表中,每个学生及其成绩是一个数据元素,其中数据元素由学号、姓名、各科成绩及平均成绩等数据项组成。

英文字母表(A,B,…,Z)是线性表,表中每个字母是一个数据元素(结点)

一副扑克牌也是一个线性表,其中数据元素是每张牌的花色和点数

4. 常见的线性表的基本运算

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

顺序表 Sequential List

1. 顺序表的定义

采用顺序存储方式存储的线性表就称为顺序表。

即把线性表的结点按逻辑次序依次存放在一组地址连续的存储单元里的方法。

线性表中所有结点的类型相同,每个结点所占用存储空间大小亦相同。

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

loc(ai)=loc(a1)+(i-1)*len   1≤i≤n 

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

2. 顺序表图示

3. 顺序表的实现

#define MAXSIZE 100
typedef int ElemType;
typedef struct{
    ElemType  elem[MAXSIZE];
    int  last; //最后一个元素在数组中的位置
    //int size; //表示数组中元素个数
}seq_list;

4. 说明

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

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

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

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

5. 初始化

void init_list( seq_list * slt) {
    slt->size=0;
}

6. 顺序表的遍历

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

7. 判断顺序表是否为空

int empty_list ( seq_list  *slt ) {
     return  slt->size==0;
}

8. 查找顺序表中值为x的结点位置

int locate_list(seq_list *slt, ElemType x)
{
    int i=0;
    while(i < slt->size && slt->elem[i]!=x) 
        i++;
    return i<=slt->last? i+1:-1;
}

9. 取得顺序表中第i个结点的值

ElemType get_data(seq_list *slt, int position) {
    position--;
    if(position < 0 || position >= slt->size){
        ElemType x;
        printf("invalid position!");
        return x;
    } else
        return slt->elem[position];
}

10. 顺序表任意位置插入

void insert_list(seq_list *slt, int position, ElemType x) { 
    int i;
    position--;
    if(slt->size == MAXSIZE){ 
        printf("Full"); return;
    }
    if(position < 0 || position > slt->size) {
        printf("invalid position!"); return;
    }
    for(i = slt->size; i > position; i--) 
        slt->elem[i] = slt->elem[i-1];
    slt->elem[position]=x;
    slt->size++;
}

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

12. 删除顺序表中任意元素

void delete_list(seq_list *slt, int position) {
    int i;
    position--;
    if(slt->size==0) {
        printf("empty");return;
    }
    if(position<0 || position >= slt->size) {
        printf("invalid position!");return;
    }
    for(i=position; i<slt->size-1; i++) 
        slt->elem[i]=slt->elem[i+1];
    slt->size--;
}

13. 删除算法时间效率

14. 顺序表的用法举例

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. 数据结构实现

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

6. 初始化

   1 void init_list(LinkList *L) {
   2    *L = (LinkList)malloc(sizeof(Node));
   3    (*L)->next = NULL;
   4 }

7. 插入算法

   1 void insert_list(Node *q, ElemType x) 
   2 {
   3    if(q==NULL) {
   4       return;
   5    }
   6    p = (Node*)malloc(sizeof(Node));
   7    p->data = x;
   8    p->next = q->next;
   9    q->next = p;
  10 }

8. 插入特定位置

   1 void insert_list_pos(LinkList node, int pos, ElemType x) 
   2 {
   3    while(pos > 1) {
   4         node = node->next;
   5         pos--;
   6    }
   7    /*node = get_data(node, pos-1);*/
   8    insert_list(node, x);
   9 }

9. 删除算法

   1 void delete_list(Node *pre) 
   2 {
   3    Node *p;
   4    if(pre==NULL || pre->next == NULL)
   5       return;
   6    p = pre->next;
   7    pre->next=p->next;
   8    free(p);
   9 }

10. 删除特定位置元素算法

   1 void delete_list_pos(Node *node, int pos) 
   2 {
   3    while(pos > 1) {
   4       node = node->next;
   5       pos--;
   6    }
   7    /*node=get_data(node, pos-1);*/
   8    delete_list(head);
   9 }

11. 删除特定值的元素算法

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

12. 查找算法

   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, ElemType x) 
   2 {
   3    head = head->next;
   4    while(head!=NULL && 
   5       head->data != x) 
   6    {
   7       head=head->next;
   8    }
   9    return head;
  10 }

13. 思考

如何实现:

不带头结点的单链表

不设开始的头结点,head指针直接指向第一个元素。

插入、删除操作比较繁琐

循环单链表

如果希望从表中的任意一个结点开始,都能访问到表中的所有其它结点,可以设置表中最后一个结点的指针域指向表中的第一个结点,这种链表称为循环单链表(或单循环链表)

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

2. 循环单链表插入

3. 循环单链表的删除

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

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

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

ch3n2k.com | Copyright (c) 2004-2020 czk.