数据结构学习(二):线性表

二、线性表的两种存储结构
2.链式存储结构
定义:为了表示每个数据元素ai与其直接后继数据元素ai+1之间的逻辑关系,对数据元素ai来说,除了存储本身的信息之外,还需存储一个指示其直接后继的信息。我们将存储数据元素信息的域成为数据域,把存储直接后继位置的域称为指针域。指针域中存储的信息称作指针或链。这两部分信息组成数据元素ai的存储映像,称为结点Node。
n个结点链接成一个链表,即为线性表(a1,a2,a3,…,an)的链式存储结构,因为此链表的每个结点只包含一个指针域,所以又叫单链表。
线性表的链式存储结构
对于线性表来说,第一个结点的存储位置叫做头指针,最后一个结点指针为空(通常用NULL或^符号表示)。
下面给出线性表链式存储结构代码描述:
Node结构体的定义如下:

#ifndef _LINK_LIST_H
#define _LINK_LIST_H
#define TRUE 0
#define FALSE -1

typedef int ElemType;
typedef int BOOL;

typedef struct Node
{
    ElemType data;
    struct Node * next;
}Node;

typedef struct Node * LinkList;

/*基于上述链表结构体的操作的声明:*/
/*ADT 中的实现*/;
extern BOOL ListInit(LinkList *L);
extern BOOL ListEmpty(LinkList L);
extern void ClearList(LinkList *L);
extern BOOL GetElem(LinkList *L, int i, ElemType * e);
extern int LocateElem(LinkList *L,ElemType e);
extern BOOL ListInsert(LinkList *L, int i, ElemType e);
extern BOOL ListDelete(LinkList *L, int i, ElemType *e);
extern int ListLength(LinkList L);
/*ADT上的操作的扩展*/

#endif

LinkList.h

单链表的操作的实现如下:

/**
 * File name: LinkList.c
 * Author:MusaGeek    Version: 1.0       Date: 2018-11-21
 * Description:  单链表的操作的实现的.c文件
 * Function List:  // 主要函数列表,每条记录应包括函数名及功能简要说明

    BOOL ListInit(LinkList *L);                     初始化链表
    BOOL ListEmpty(LinkList L);                     判断链表是否为空
    void ClearList(LinkList *L);                     清空链表
    BOOL GetElem(LinkList *L, int i, ElemType * e); 获取链表指定位置的元素
    int LocateElem(LinkList *L,ElemType e);         定位元素的位置
    BOOL ListInsert(LinkList *L, int i, ElemType e);插入元素
    BOOL ListDelete(LinkList *L, int i, ElemType *e);删除元素
    int ListLength(LinkList L);                         获取链表的长度
*/

#include <stdio.h>
#include <stdlib.h>
#include "LinkList.h"

/*************************************************
Function: ListInit
Description: 初始化单链表中的数据 
             1.为指向链表的头指针分配头结点空间
             2.将头结点的指针域设置为NULL
Input: 
      L  : 指向链表头结点的指针
Return: 成功TRUE | 失败 FALSE
*************************************************/
BOOL ListInit(LinkList *L)
{
    Node *head = (Node *)malloc(sizeof(Node));
    if(!head)
        return FALSE;
    *L = head;
    head->next = NULL;
    return TRUE;
}


/*************************************************
Function: ListInit
Description: 初始化单链表中的数据 
             1.为指向链表的头指针分配头结点空间
             2.将头结点的指针域设置为NULL
Input: 
      L  : 指向链表头结点的指针
Return: 成功TRUE | 失败 FALSE
*************************************************/
BOOL ListEmpty(LinkList L)
{
    return ( (L->next) ? FALSE : TRUE);
}


/*************************************************
Function:  ClearList
Description: 因为 LinkList 中的结点均是malloc而来的 , 因此当整个链表不使用了时候 需要手动释放掉,避免消耗内存
             但是保留头结点
             1.创建 指针 p = (*L)->next , 指针 q;
             2.循环 :当 p 不为 NULL 时候 ; q = p->next;释放 p; p = q;
Input: 
      L  : 指向链表头结点的指针  
Return: void
*************************************************/
void ClearList(LinkList *L)

{
    Node *p = (*L) = (*L)->next, *q = NULL;
    while(p)
    {
        q = p->next;
        free(p);
        p = q;
    }
    (*L)->next = NULL; /*头结点不释放 ,指针域置为NULL*/
}


/*************************************************
Function: GetElem
Description: 获取顺序表中指定位置的元素 , 时间复杂度O(n) , 时间浪费在遍历上面:
            1.声明一个结点p指向链表的第一个结点 , 初始化 j 从1开始
            2.当 j < i 时 , 就遍历链表 , 让指针p不断的向后移动 , j+=1
            3.若最终 NULL == p 则 返回FALSE
            4.若 NULL != p 则 将data域数据传出 返回TRUE , 
Input: 
      L  : 指向链表头结点的指针
      i  : 获取元素的位置  
Output: 
      *e : 输出类型参数 , 获取 i 指向的元素  
Return: 成功TRUE | 失败 FALSE
*************************************************/
BOOL GetElem(LinkList *L, int i, ElemType * e)
{
    Node * p = (*L)->next;
    int j = 1;
    while(p && j < i)
    {
        p = p->next;
        j++;
    }
    *e = p->next->data;
    return (p ? TRUE : FALSE);  
}


/*************************************************
Function: LocateElem
Description: 定位e在链表中的位置, 时间复杂度O(n), 时间浪费在遍历上面:
             遍历找到第一个匹配的元素即可   
Input: 
      L: 指向链表头结点的指针的指针
      e: 需要定位的元素值
Return: int 元素在链表中的位置,没有匹配到为-1
*************************************************/
int LocateElem(LinkList *L,ElemType e)
{
    Node *p = (*L)->next;
    int j =  1;
    while(p && p->data != e)
    {
        p = p->next;
        j++;
    }
    return p ? j : -1;
}


/*************************************************
Function:  ListInsert
Description: 在指定 i 的位置插入元素 , O(n) , 浪费的时间在遍历上面 , 但是没有移动元素造成的复制开销
            
              1.声明一个结点 p 指向链表的头结结点 , 初始化 j 从 1 开始
              2.当 j < i 遍历链表 , 让p指针不断后移 , j+=1
              3.若 遍历结束 , NULL == p , 返回FALSE
              4.否则 当 j == i 时候 , p 已经指向的了 第i个元素之前的元素
            5.创建结点s(malloc) , s->next = p->next; p->next = s;
            6.返回TRUE 
Input: 
      L  : 指向链表头结点的指针
      i  : 获取元素的位置  
      e  : 插入的元素值  
Output: 
     
Return: 成功TRUE | 失败 FALSE
*************************************************/
BOOL ListInsert(LinkList *L, int i, ElemType e)
{
    Node *p = *L , *s = NULL;
    int j = 1;
    while(p && j<i)                           //如果 i 有效的话 ,那么p最终会指向第i个结点前面的那个结点
    {
        p = p->next;
        j++;
    }
    
    if(p)                                
    {
        s = (struct Node *)malloc(sizeof(Node));    //给新插入的结点分配空间
        s->data = e;
        s->next = p->next;                                   //下面两步骤不能反了
        p->next = s;
        return TRUE;
    }
    return FALSE;
}


/*************************************************
Function:  ListDelete
Description: 删除指定的第 i 个位置的元素 , O(n) 但是没有移动元素复制元素的开销
            
            1.定义指针p指向第一个结点 , 定义 j = 1;
            2.在 j < i 且 p !=NULL 的情况下 让指针p向后移动 , j++
            3.当 j >= i 时 若 NULL = p 那么 返回FALSE
            4.否则 临时指针变量 t 保存 p->next;p->next = p->next->next; 再释放t
            5.返回TRUE       
Input: 
      L  : 链表头指针
      i  : 获取元素的位置  
Output: 
      *e : 输出类型参数 , 获取 i 指向的元素  
Return: 成功TRUE | 失败 FALSE
*************************************************/
BOOL ListDelete(LinkList *L, int i, ElemType *e)
{
    Node *p = *L, *t = NULL;
    int j = 1;
    while(p && j < i)     //如果 i 有效的话 ,那么p最终会指向第i个结点前面的那个结点
    {
        p = p->next;
        j++;
    }
    if(p)
    {
        t = p->next;
        p->next = t->next;  //将要删除结点剔除链表
        *e = t->data;
        free(t);
        return TRUE;
    }
    return FALSE;
}


/*************************************************
Function:  ListLength
Description: 获取链表的长度
             直接遍历链表获得
Input: 
      L  : 链表头指针
Return: int 链表的长度
*************************************************/
int ListLength(LinkList L)
{
    Node *p = L->next;
    int len = 0;
    while(p)
    {
        len++;
        p = p->next;
    }
    return len;
}

LinkList.c

单链表虽然在插入,删除元素上面的操作也是O(n) , 但是只是时间花费在了遍历的开销上面了,而不是元素移动上,且如果元素的类型越复杂,移动上面造成复制的开销将更大,因此单链表 比 顺序表更适合频繁的元素插入的和删除。
单链表的创建又分为 头插法 和 尾插法 两种创建方式

顺序表 和 链表的对比 :
频繁查找 , 很少进行删除 和 插入时 适用顺序表
线性表的元素变化较大 , 频繁的进行插入和删除 ,使用链表
线性表不知道元素的个数的多少的时候 , 就使用链表

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值