数据结构线性表初接触

刚刚接触数据结构这学问,通过听课看书自行练习了用C语言实现线性表的基本运算,其中有许多点值得注意。这里存储各个功能分块的代码,以便自身的理解及以后的复习;

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

 

线性表顺序存储结构代码描述

线性表的顺序存储的结构代码:

/* 存储空间初始分配量 */
#define MAXSIZE 20             
/* ElemType类型根据实际情况而定,这里假设为int */
typedef int ElemType;          
typedef struct
{
    /* 数组存储数据元素,最大值为MAXSIZE */
    ElemType data[MAXSIZE];    
    /* 线性表当前长度 */
    int length;                
} SqList;
typedef

可以发现描述顺序存储结构需要三个属性:

  • 存储空间的起始位置:数组data,它的存储位置就是存储空间的存储位置。
  • 线性表的最大存储容量:数组长度MaxSize。
  • 线性表的当前长度:length。

对于线性表的顺序存储结构来说,要实现GetElem操作,即将线性表L中的第i个位置元素值返回,就程序而言,只要i的数值在数组下标范围内,就是把数组第i-1下标的值返回即可。来看代码:

#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
typedef int Status;
/* Status是函数的类型,其值是函数结果状态代
   码,如OK等 */
/* 初始条件:顺序线性表L已存在,1≤i≤
   ListLength(L) */
/* 操作结果:用e返回L中第i个数据元素的值 */
Status GetElem(SqList L, int i, ElemType *e)
{
    if (L.length == 0 || i < 1 || 
        i > L.length)
        return ERROR;
    *e = L.data[i - 1];
    return OK;
}
GetElem

这里返回值类型Status是一个整型,返回OK代表1,ERROR代表0。之后代码中出现就不再详述。

如果要实现ListIn-sert(*L,i,e),即在线性表L中的第i个位置插入新元素e,

插入算法的思路:

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

实现代码如下:

/* 初始条件:顺序线性表L已存在,1≤i≤
   ListLength(L), */
/* 操作结果:在L中第i个位置之前插入新的数据元
   素e,L的长度加1 */
Status ListInsert(SqList *L, int i, ElemType e)
{
    int k;
    /* 顺序线性表已经满 */
    if (L->length == MAXSIZE)                       
        return ERROR;
    /* 当i不在范围内时 */
    if (i < 1 || i >L->length + 1)                  
        return ERROR;
    /* 若插入数据位置不在表尾 */
    if (i <= L->length)                             
    {
        /*将要插入位置后数据元素向后移动一位 */
        for (k = L->length - 1; k >= i - 1; k--)    
            L->data[k + 1] = L->data[k];
    }
    /* 将新元素插入 */
    L->data[i - 1] = e;                             
    L->length++;
    return OK;
}
ListInsert

线性表的顺序存储结构删除元素

删除算法的思路:

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

实现代码如下:

/* 初始条件:顺序线性表L已存在,1≤i≤
   ListLength(L) */
/* 操作结果:删除L的第i个数据元素,并用e返回
   其值,L的长度减1 */
Status ListDelete(SqList *L, int i, ElemType *e)
{
    int k;
    /* 线性表为空 */
    if (L->length == 0)                    
           return ERROR;
    /* 删除位置不正确 */
    if (i < 1 || i > L->length)            
        return ERROR;
    *e = L->data[i - 1];
    /* 如果删除不是最后位置 */
    if (i < L->length)                     
    {
        /* 将删除位置后继元素前移 */
        for (k = i; k < L->length; k++)    
            L->data[k - 1] = L->data[k];
    }
    L->length--;
    return OK;
}
ListDelete

线性表的顺序存储结构,在存、读数据时,不管是哪个位置,时间复杂度都是O(1);而插入或删除时,时间复杂度都是O(n)。这就说明,它比较适合元素个数不太变化,而更多是存取数据的应用。当然,它的优缺点还不只这些……

 

线性表链式存储结构代码描述

单链表中,我们在C语言中可用结构指针来描述。

/* 线性表的单链表存储结构 */
typedef struct Node
{
    ElemType data;
    struct Node *next;
} Node;
/* 定义LinkList */
typedef struct Node *LinkList;
typedef

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

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

实现代码算法如下:

/* 初始条件:顺序线性表L已存在,1≤i≤
   ListLength(L) */
/* 操作结果:用e返回L中第i个数据元素的值 */
Status GetElem(LinkList L, int i, ElemType *e)
{
    int j;
    LinkList p;            /* 声明一指针p */
    p = L->next;        /* 让p指向链表L的第个结点 */
    j = 1;                 /* j为计数器 */
    /* p不为空且计数器j还没有等于i时,循环继续 */
    while (p && j < i)    
    {
        p = p->next;    /* 让p指向下一个结点 */
        ++j;
    }
    if (!p || j > i)
        return ERROR;      /* 第i个结点不存在 */
    *e = p->data;       /* 取第i个结点的数据 */
    return OK;
}
GetElem

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

1.声明一指针p指向链表头结点,初始化j从1开始;

2.当j<i时,就遍历链表,让p的指针向后移动,不断指向下一结点,j累加1;

3.若到链表末尾p为空,则说明第i个结点不存在;

4.否则查找成功,在系统中生成一个空结点s;

5.将数据元素e赋值给s->data;

6.单链表的插入标准语句s->next=p->next;p->next=s;

7.返回成功。

实现代码算法如下:

/* 初始条件:顺序线性表L已存在,1≤i≤
   ListLength(L), */
/* 操作结果:在L中第i个结点位置之前插入新的数
   据元素e,L的长度加1 */
Status ListInsert(LinkList *L, int i, ElemType e)
{
    int j;
    LinkList p, s;
    p = *L;
    j = 1;
    /* 寻找第i-1个结点 */
    while (p && j < i)                     
    {
        p = p->next;
        ++j;
    }
    /* 第i个结点不存在 */
    if (!p || j > i)
        return ERROR;                      
    /* 生成新结点(C标准函数) */
    s = (LinkList)malloc(sizeof(Node));    
    s->data = e;
    /* 将p的后继结点赋值给s的后继 */
    s->next = p->next;                    
    /* 将s赋值给p的后继 */
    p->next = s;                           
    return OK;
}    
ListInsert

单链表第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.返回成功。

实现代码算法如下:

/* 初始条件:顺序线性表L已存在,1≤i≤
   ListLength(L) */
/* 操作结果:删除L的第i个结点,并用e返回其
   值,L的长度减1 */
Status ListDelete(LinkList *L, int i, ElemType *e)
{
    int j;
    LinkList p, q;
    p = *L;
    j = 1;
    /* 遍历寻找第i-1个结点 */
    while (p->next && j < i)    
    {
        p = p->next;
        ++j;
    }
    /* 第i个结点不存在 */
    if (!(p->next) || j > i)
        return ERROR;           
    q = p->next;
    /* 将q的后继赋值给p的后继 */
    p->next = q->next;          
    /* 将q结点中的数据给e */
    *e = q->data;               
    /* 让系统回收此结点,释放内存 */
    free(q);                    
    return OK;
}
ListDelete

回顾一下,顺序存储结构的创建,其实就是一个数组的初始化,即声明一个类型和大小的数组并赋值的过程。而单链表和顺序存储结构就不一样,它不像顺序存储结构这么集中,它可以很散,是一种动态结构。对于每个链表来说,它所占用空间的大小和位置是不需要预先分配划定的,可以根据系统的情况和实际的需求即时生成。

所以创建单链表的过程就是一个动态生成链表的过程。即从“空表”的初始状态起,依次建立各元素结点,并逐个插入链表。

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

1.声明一指针p和计数器变量i;
2.初始化一空链表L;
3.让L的头结点的指针指向NULL,即建立一个带头结点的单链表;
4.循环:

  • 生成一新结点赋值给p;
  • 随机生成一数字赋值给p的数据域p->data;
  • 将p插入到头结点与前一新结点之间。

实现代码算法如下:

头插法:始终让新结点在第一的位置。

/* 随机产生n个元素的值,建立带表头结点的单链
   线性表L(头插法) */
void CreateListHead(LinkList *L, int n)
{
    LinkList p;
    int i;
    /* 初始化随机数种子 */
    srand(time(0));                            
    *L = (LinkList)malloc(sizeof(Node));
    /* 先建立一个带头结点的单链表 */
    (*L)->next = NULL;                         
    for (i = 0; i < n; i++)
    {
        /* 生成新结点 */
        p = (LinkList)malloc(sizeof(Node));    
        /* 随机生成100以内的数字 */
        p->data = rand() % 100 + 1;            
        p->next = (*L)->next;
        /* 插入到表头 */
        (*L)->next = p;                        
    }
}    
CreateListHead

尾插法:把每次新结点都插在终端结点的后面

CreateListTail

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

1.声明一指针p和q;
2.将第一个结点赋值给p;
3.循环:

  • 将下一结点赋值给q;
  • 释放p;
  • 将q赋值给p。

实现代码算法如下:

/* 初始条件:顺序线性表L已存在,操作结果:将L
   重置为空表 */
Status ClearList(LinkList *L)
{
    LinkList p, q;
    /* p指向第一个结点 */
    p = (*L)->next;       
    /* 没到表尾 */
    while (p)             
    {
        q = p->next;
        free(p);
        p=q;
    }
    /* 头结点指针域为空 */
    (*L)->next = NULL;    
    return OK;
}
ClearList

通过上面的对比,我们可以得出一些经验性的结论:

  • 若线性表需要频繁查找,很少进行插入和删除操作时,宜采用顺序存储结构。若需要频繁插入和删除时,宜采用单链表结构。比如说游戏开发中,对于用户注册的个人信息,除了注册时插入数据外,绝大多数情况都是读取,所以应该考虑用顺序存储结构。而游戏中的玩家的武器或者装备列表,随着玩家的游戏过程中,可能会随时增加或删除,此时再用顺序存储就不太合适了,单链表结构就可以大展拳脚。当然,这只是简单的类比,现实中的软件开发,要考虑的问题会复杂得多。
  • 当线性表中的元素个数变化较大或者根本不知道有多大时,最好用单链表结构,这样可以不需要考虑存储空间的大小问题。而如果事先知道线性表的大致长度,比如一年12个月,一周就是星期一至星期日共七天,这种用顺序存储结构效率会高很多。

总之,线性表的顺序存储结构和单链表结构各有其优缺点,不能简单的说哪个好,哪个不好,需要根据实际情况,来综合平衡采用哪种数据结构更能满足和达到需求和性能。

而另外,还有静态链表和循环链表,双向链表等都可像上面的基本操作上推导出来。

 

posted @ 2018-07-26 11:17  Z_a_c_k  阅读(117)  评论(0)    收藏  举报