链表


这学期这门程序设计基础一组合后变难了。这篇文章将PPT内容移植过来,方便阅读


数据结构的一些基本概念

计算机应用中,数值、文字、图形、图像、声音等各种形式的信息是对客观事物的符号化编码表示,也称为数据(data)。

构成数据的、具有相同性质的基本单元称为数据元素(data element)。

数据项(data item)是构成数据的相对独立的分项,它反映客观事物的某种特性。

性质相同的数据元素集合组成数据对象(data object)。

如出版社图书信息管理系统中已出版图书列表,每本书的信息是一个数据元素,由书名、作者、定价信息、出版时间等多个数据项组成。所有这些数据元素构成数据对象,表示该出版社出版的所有图书。

数据对象内的数据元素间可以存在一种或多种关系。数据结构(data structure)是相互之间存在一种或多种特定关系的数据元素的集合,包含数据对象和关系两个组成部分。

数据结构可以用二元组来表示:

Data Structure = (D, S)

其中

在这里插入图片描述

逻辑结构基本概念

用二元组描述的数据结构体现出数据元素间的逻辑关系,称为逻辑结构

4种基本逻辑结构:线性结构、集合结构、树型结构和图状结构

数据结构只有在计算机物理存储器上存储表示后,才能设计程序进行处理,数据结构在计算机物理存储器里的实际存储方案称为存储结构。存储结构需要存储(表示)数据对象和数据元素间的各种关系。

同一种逻辑结构,可以采用不同的存储结构。通常采用两种存储结构:

顺序存储结构。所有数据元素在内存空间中依次存放,数据元素在物理存储器上的位置关系体现了它们在逻辑上的关系,通常用于表示简单的顺序关系。
链式存储结构。数据元素分散存放,在存放每个数据元素时,必须附加一个或若干专门的数据项来指示其它相关联的数据元素在存储器中的存放位置。

在这里插入图片描述


线性表

线性表的定义

在这里插入图片描述

线性表是n个数据元素的有限序列,可记为:在这里插入图片描述n是线性表的长度。当n = 0时,为一空表。

线性表的抽象数据类型表示

数据类型(data type)和抽象数据类型(abstract data type, ADT)

在这里插入图片描述

线性表的抽象数据类型定义

 ADT List {  
 基本操作:
    创建空表Create () ;       //创建一个空的线性表
    清空Clear (L) ;           //将已有线性表L清空
    销毁线性表Destroy (L);    //销毁一个线性表L,不再使用
    拷贝线性表Copy (L) ;      //根据已有线性表L,复制一个新线性表,内容相同
    判空IsEmpty (L) ;         //判断线性表L是否为空表,若是则返回TRUE,否则返回FALSE
    求长度Length (L);         //返回线性表中数据元素的个数
    获取起始位置BeginPosition (L) ;  //返回线性表中代表第一个元素的位置
			                        //空表返回EndPosition (L)
    获取结束位置EndPosition (L);   //返回代表线性表结束的位置
    迭代下一位置NextPosition (L,pos); //返回线性表中pos有效位置的下个位置
			                         //结束位置返回EndPosition (L)
    获取元素位置LocatePosition (L,i);  //返回线性表代表第i个元素所在位置,1≤i≤n
                                      //(设线性表的表长为n)
    定位元素位置LocateElem(L,e) ; //根据数据元素e查找它在线性表中出现的位置
                                 //若存在,则返回它的有效位置;否则返回EndPosition (L)
    获取元素GetElem (L,pos) ;   //返回线性表中pos有效位置的数据元素
    设置元素SetElem (L,pos,e);  //将线性表中pos有效位置的数据元素设置为e
    插入元素InsertBefore (L,pos,e); //在线性表的pos位置前插入一新的数据元素
                                   //pos为EndPosition (L)时添加在尾部
    删除元素Delete(L,pos) ;        //删除线性表中pos有效位置所在数据元素
} 

线性表的顺序储存

用一组地址连续的存储单元依次存放线性表中的数据元素。
在这里插入图片描述

线性表中任意一个数据元素的存储位置都可以用线性表中第一个数据元素a1的存储地址加上偏移表示。其中:K为一个数据元素所占存储量。

在这里插入图片描述

线性表抽象数据类型List可以采用顺序存储结构实现。

线性表的顺序表示声明模块SeqList.h

#ifndef __SEQLIST_H_INCLUDED_
#define __SEQLIST_H_INCLUDED_
typedef  int DataElem;    //假设元素类型为整型
//线性表类型
struct SeqList {
    DataElem *pDatas;   //存放元素表的缓冲区指针
    int       iLength;  //线性表长度
    int       iSize;    //缓冲区大小
};
typedef  DataElem *  Position;    //线性表中位置类型
//...在此省略线性表各基本操作的函数声明
#endif // __SEQLIST_H_INCLUDED_

线性表顺序表示基本操作

1. 创建空线性表
//建立一个最多可存放iSize个元素的空线性表,失败时缓冲区指针为NULL
struct SeqList Create (int iSize)
{   struct SeqList list;
   //申请存放线性表元素的连续内存空间
   list.pDatas = (DataElem *)malloc (iSize * sizeof (DataElem));
   list.iLength = 0;
   list.iSize = iSize;
   if (list.pDatas == NULL) {//申请不到空间
       list.iSize = 0;
   }
   return list;
}
2. 线性表清空
//元素个数置为0
void Clear (struct SeqList *pSeqList)
{
    pSeqList->iLength = 0;
}
3. 销毁一个线性表,不再使用,释放缓冲区
void Destroy (struct SeqList *pSeqList)
{
    free (pSeqList->pDatas);                  //释放缓冲区
    pSeqList->pDatas = NULL;
    pSeqList->iSize = 0;
}
4. 根据已有线性表,复制一个内容相同的新线性表
 //返回复制后新线性表,失败时返回线性表的头指针为NULL
 struct SeqList Copy (struct SeqList srcSeqList)
{   struct SeqList destSeqList;
    destSeqList = Create (srcSeqList.iSize); //创建一个具有相同大小缓冲区的空线性表
    if (destSeqList.pDatas == NULL)
        return destSeqList;  //创建失败时直接返回
    int i;
    for (i = 0; i < srcSeqList.iLength; ++i) //复制所有元素
        destSeqList.pDatas [i] = srcSeqList.pDatas [i];
    destSeqList.iLength = i; //设置线性表长度
    return destSeqList; //返回复制完的线性表
}
5. 线性表判空
int IsEmpty (struct SeqList list)
{
    return (list.iLength == 0);
}
6. 线性表求长度
int Length (struct SeqList list)
{
    return list.iLength;
}
7. 获取起始位置
//返回线性表中代表第一个元素的位置,空表返回EndPosition (L)
Position BeginPosition (struct SeqList list)
{
    return list.pDatas;
}
8. 获取结束位置
//返回代表线性表结束的位置
Position  EndPosition (struct SeqList list)
{
    return list.pDatas + list.iLength;
}
9. 迭代下一位置
//返回线性表中p有效位置的下个位置,主要用于循环遍历线性表
Position NextPosition (struct SeqList list, Position  pos)
{
    return pos+1;
}
10. 获取元素位置
//返回线性表代表第i个元素所在位置,1≤i≤n(设线性表的表长为n)
Position LocatePosition (struct SeqList list, int i)
{
    if (i >= 1 && i <= list.iLength)
        return list.pDatas + (i - 1);
    return list.pDatas + list.iLength; //超出范围,返回结束位置
}
11. 定位元素位置
//根据数据元素e查找它在线性表中出现的位置,若存在,则返回它的有效位置;
//否则返回EndPosition (L)
Position LocateElem (struct SeqList list, DataElem e)
{
    int  i;
    for (i = 0; i < list.iLength; ++i)
        if (list.pDatas [i] == e) //查找到
            return list.pDatas + i;
    return list.pDatas + list.iLength; //超出范围,返回结束位置
}
12. 获取元素
//返回线性表L中pos有效位置的数据元素
DataElem GetElem (struct SeqList list, Position  pos)
{
    assert (pos != EndPosition (list)); //断言,包含头文件assert.h
    return *pos;
}
13. 设置元素
//将线性表中pos有效位置的数据元素设置为e
void SetElem (struct SeqList list, Position  pos, DataElem e)
{
    assert (pos != EndPosition (list)); //断言,包含头文件assert.h
    *pos = e;
}
14. 插入元素
//在线性表的pos位置前插入一新的数据元素,
//pos为EndPosition (L)时添加在尾部,线性表长度加1
//成功时返回1,失败时返回0
int InsertBefore (struct SeqList *pSeqList, Position  pos, DataElem e)
{    if (pSeqList->iSize == pSeqList->iLength) {//空间已满,先扩充空间
        struct SeqList newSeqList = Create (2 * pSeqList->iSize); //建立2倍空间的临时线性表
        if (newSeqList.pDatas == NULL) {
             return 0; //申请不到空间,操作失败
        }
        int i;
        for (i = 0; i < pSeqList->iLength; ++i) //复制所有元素
             newSeqList.pDatas [i] = pSeqList->pDatas [i];
        newSeqList.iLength = i; //设置线性表长度   (待续)
        pos = newSeqList.pDatas + (pos - pSeqList->pDatas); //原pos必须更新
        Destroy(pSeqList);       //销毁原线性表,板卡它的缓冲区,避免内存泄漏
        *pSeqList = newSeqList;  //用扩充空间后线性表代替原线性表
    }
    assert (pSeqList->iSize > pSeqList->iLength); //线性表内存空间必有空余
    Position  lastPos = pSeqList->pDatas+pSeqList->iLength; //线性表后空余位置
    while (pos != lastPos) { //从后往前循环后移,直到到达指定位置
        *lastPos = *(lastPos-1); //元素后移一个位置
        --lastPos;               //准备处理前一位置元素
    }
    *pos = e;           //将元素存入已空出位置
    ++pSeqList->iLength; //调整线性表长度
    return 1;
}
15. 删除元素
//删除线性表中pos有效位置所在数据元素,线性表的表长减1
void Delete (struct SeqList *pSeqList, Position  pos)
{
    Position  endPos = EndPosition (*pSeqList);
    assert (pos != endPos); //断言,包含头文件assert.h
    ++pos; //后移一位置
    while (pos!= endPos) {
        *(pos - 1) = *pos; //后一位置元素前移
        ++pos;           //准备后移下一个元素
    }
    --pSeqList->iLength; //调整线性表长度
}
16、通过抽象数据类型SeqList的基本操作打印线性表
void Print (struct SeqList list)
{   printf ("(");
    Position pos = BeginPosition(list);
    if (pos != EndPosition(list)) {
        printf ("%d", GetElem (list, pos));
        pos = NextPosition(list, pos);
    }
    while (pos != EndPosition(list)) {
        printf (",%d", GetElem (list, pos));
        pos = NextPosition(list, pos);
    }
    printf (")\n");
}
主函数
int main()
{   int i;
    struct SeqList  list = Create (1);       //创建一个空线性表
    Print (list);  //打印线性表
    for (i = 1; i <=5; ++i)//尾部循环插入i
        InsertBefore (&list, EndPosition(list), i);
    Print (list);
    Position pos = LocateElem(list, 3); //查找元素3
    if (pos != EndPosition(list))    Delete (&list, pos); //查找到元素时,删除元素
    InsertBefore (&list, BeginPosition(list), 8); //头部插入元素8
    Print (list);
    Destroy (&list); //销毁线性表,避免内存泄漏
    return 0;
}

线性表的链式存储

采用链式存储结构,有利于线性表的插入、删除操作,每个结点中只有一个“链”时,称为单链表,每个结点中有两个“链”时,称为双向链表。“链”通常用指针来表示,线性表的链式存储结构也是实现复杂数据结构的基础。

单链表的概念

在这里插入图片描述
一般结点类型定义如下:

struct Node {
	DataElem  data;
	struct Node *next;
};
struct Node    *p, *q; //声明指针型变量p、q

注意事项有:
特别需要注意指针变量p和p所指结点的区别。指针变量p和p所指结点是独立存在的
当p是空指针时,禁止通过空指针访问结点内数据域或指针域
通过非空指针变量p可访问结点的数据域p->data和指针域p->next
p->data和p->next既可作为右值、又可作为左值出现在表达式中。右值和左值是表达式中语法单位,意味着可出现在赋值运算符右边和左边
p->next还是一个指针,只要它非空,可以继续通过它访问所指结点的数据域和指针域,如p->next->data和p->next->next
指针变量p未初始化时,不可访问p->next或p->data,否则,可能造成程序崩溃
C/C++语言中,动态分配的结点必须释放,否则造成内存泄漏

常见的指针操作及其效果

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

对结点的基本操作

线性表中插入元素时,需要在链表中增加结点,用下述语句动态分配:

p = (struct Node *) malloc (sizeof (struct Node));

动态分配失败时,返回空指针(NULL),现代面向对象程序设计语言中,如C++,一般按异常处理。
下列语句完成将p所指结点插入q所指结点后:

p->next = q->next; //p后继结点设为原q后继结点
q->next = p;            //q后继结点设为p

下列语句删除p所指结点后继结点:

q = p->next;                       //保存后继结点指针
p->next = q->next;            //链表中去除q所指结点
free (q);                               //释放q所指结点

单链表实现ADT List

在这里插入图片描述

链表结点类型
struct Node {
    DataElem  data;
    struct Node *next;
};
线性表类型
struct List {
    struct Node *pHead;
    struct Node *pTail;
};
typedef  struct Node *  Position;    //线性表中位置类型
1. 创建空线性表
//空表管理的单链表只有一个头结点,失败时首尾指针为NULL
struct List Create ()
{
    struct List list;
    //申请一个结点
    list.pHead = list.pTail = (struct Node *)malloc (sizeof (struct Node));
    if (list.pHead != NULL)
        list.pHead->next = NULL; //后续无结点
    return list;
}
2. 线性表清空
//释放链表中除头结点外所有结点
void Clear (struct List *pList)
{   struct Node *p = pList->pHead->next; //从头结点后结点开始删除
    while (p != NULL) {     //直到最后
        struct Node *q = p; //记住要释放结点
        p = p->next;        //准备释放下一个
        free (q);           //释放结点,释放后不可再访问结点
    }
    pList->pHead->next = NULL; //头结点后已无结点
    pList->pTail = pList->pHead;
}
3. 销毁一个线性表,不再使用,释放所有结点
//传入链首指针变量的地址,销毁单链表
void Destroy (struct List *pList)
{
    Clear (pList);                        //单链表清空
    free (pList->pHead);           //释放最后剩余的头结点
    pList->pHead = pList->pTail = NULL; //将线性表中指针变量置空
}
4. 根据已有线性表,复制一个内容相同的新线性表
//传入原线性表,复制原线性表中链表作为新线性表的链表,
//返回复制后新线性表,失败时返回线性表的头指针为NULL,
struct List Copy (struct List srcList)
{   struct List destList;
    destList = Create (); //创建带头结点的空单链表
    if (destList.pHead == NULL)
        return destList;  //创建失败时直接返回
    struct Node  *p;
    p = srcList.pHead->next; //跳过原链表头结点
    while (p != NULL) {//循环处理原链表
        struct Node *s;
        s = (struct Node *)malloc (sizeof (struct Node));//分配新结点
   (待续)
    if (s == NULL) {
            Destroy (&destList); //申请失败时销毁线性表,避免内存泄漏
            return destList;
        }
        s->data = p->data; //复制元素
        destList.pTail->next = s; //挂在新链表最后
        s->next = NULL; //后面无结点
        destList.pTail = s;       //最后一个结点已变化
        p = p->next;    //准备复制下个结点
    }
    return destList; //返回复制完的线性表
}
5. 线性表判空
判断线性表是否为空表,若是则返回1,否则返回0
int IsEmpty (struct List list)
{
    return (list.pHead->next == NULL); //头结点后无结点
}
6. 线性表求长度
返回线性表中数据元素的个数
int Length (struct List list)
{
    int iCount = 0;
    struct Node *p = list.pHead->next; //从头结点后结点开始计数
    while (p != NULL) {     //直到最后
        ++iCount;
        p = p->next;        //临时变量移至下一结点
    }
    return iCount;
}
7. 获取起始位置
//返回线性表中代表第一个元素的位置,空表返回EndPosition (L)
Position BeginPosition (struct List list)
{
    return list.pHead;
}
8. 获取结束位置
//返回代表线性表结束的位置
Position  EndPosition (struct List list)
{
    return list.pTail;
}
9. 迭代下一位置
//返回线性表中p有效位置的下个位置,主要用于循环遍历线性表
Position NextPosition (struct List list, Position  pos)
{
    return pos->next;
}
10. 获取元素位置
//返回线性表代表第i个元素所在位置,1≤i≤n(设线性表的表长为n)
Position LocatePosition (struct List list, int i)
{
    Position  pos = list.pHead; //从头结点开始
    while (--i> 0 && pos->next != NULL) {//计数完成或直到结束
        pos = pos->next;        //移至下一结点
    }
    return pos;
}
11. 定位元素位置
//根据数据元素e查找它在线性表中出现的位置,若存在,则返回它的有效位置;
//否则返回EndPosition (L)
Position LocateElem (struct List list, DataElem e)
{
    Position  pos = list.pHead; //从头结点开始
    while (pos->next != NULL && pos->next->data != e) {
        pos = pos->next;        //移至下一结点
    }
    return pos; //返回元素所在结点的前一个结点指针
}
12. 获取元素
//返回线性表L中pos有效位置的数据元素
DataElem GetElem (struct List list, Position  pos)
{   assert (pos != EndPosition (list)); //断言,包含头文件assert.h
    return pos->next->data;
}
13. 设置元素
//将线性表中pos有效位置的数据元素设置为e
void SetElem (struct List list, Position  pos, DataElem e)
{   assert (pos != EndPosition (list)); //断言,包含头文件assert.h
    pos->next->data = e;
}

####14. 插入元素

//在线性表的pos位置前插入一新的数据元素,
//pos为EndPosition (L)时添加在尾部,线性表长度加1
//成功时返回1,失败时返回0
int InsertBefore (struct List *pList, Position  pos, DataElem e)
{   struct Node *s = (struct Node *)malloc (sizeof (struct Node));//分配新结点
    if  (s == NULL)  return 0;
    s->data = e;
    s->next = pos->next; //插入在pos所指结点后
    pos->next = s;
    if (pList->pTail == pos)
        pList->pTail = s; //插入在线性表最后时,调整尾结点指针
    return 1;
}
15. 删除元素
//删除线性表中pos有效位置所在数据元素,线性表的表长减1
void Delete (struct List *pList, Position  pos)
{   assert (pos != EndPosition (*pList)); //断言,包含头文件assert.h
    struct Node *s = pos->next; //待删除结点
    pos->next = s->next;        //链表中删除结点
    free (s);                 //释放结点
    if (pList->pTail == s)
        pList->pTail = pos; //删除线性表最后一个结点时,调整尾结点指针
}

采用单链表实现ADT List的测试用例与采用顺序存储结构实现ADT List的测试用例基本相同,只是类型名称不同,输出结果也相同


# 总结 提示:这里对文章进行总结: 例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值