数据结构学习笔记(一)—— 线性表

线性表 链表

开始更新我的数据结构学习笔记,因为前段时间太忙了,正好最近数据结构也快要考试了,也当复习吧~

一.单链表

结构体形式

typedef struct LNode
{
    ElemType data;//数据域
    struct LNode *next;//指针域
}LNode;
typedef LNode *LinkList;

说明:LinkList 和 LNode*是不同名字的同一个指针类型。LinkList 类型的指针变量表示是单链表的头指针,LNode * 类型的指针变量表示是指向某一结点的指针。
即:LNode *p = LinkList p

初始化操作

//时间复杂度为O(1)
//生成一个新结点作为头结点
//设置头结点的指针域为空
void Init(LinkList &L)
{
    //构造一个单链表L
    L = new LNode;
    L->next = NULL;
}

销毁操作

//时间复杂度为O(n)
void Destroy(LinkList &L)
{
    //释放单链表L所占用的存储空间
    LinkList p;
    while(L)
    {
       p = L;
       L = L->next;
       delete p;
    }
}

清空操作

清空操作不释放头结点,销毁操作释放头结点

//时间复杂度为O(n)
void clear(LinkList &L)
{
    LinkList p,q;
    p = L->next;//p指向第一个结点
    while(p)
    {
       q = p->next;
       delete p;
       p = q;
    }
    L->next = NULL;//令头指针的节点域为空
}

求表长

//时间复杂度为O(n)
int ListLength(LinkList L)
{
     LinkList p;
     p = L->next;//设置指针,初始指向L的第一个结点
     length = 0;
     while(p)
     {
        // 顺表向后扫描遍历,计算表长
        length++;
        p = p->next; 
      }
      return length;
}

取值操作

//时间复杂度为O(n),ASL = (n-1)/2
void GetElem_L(LinkList L,int i,ElemType &e) 
{
    // 用e返回单链表L中第i个结点的数据域值;
   //若i值不合理,则给出相应信息并退出运行,i的合理值为1≤i≤表长
    LinkList  p = L->next;	// 设置指针,初始时指向L的第一个结点
    int j=1;			// 设置计数器,初始时为1
    while(p&&(j<i))  
    {		// 顺链向后扫描
        p=p->next;  //p指向下一个结点
        ++j;     //计数器j相应加1
    }
    if(!p||(j>i))  Error(" Position Error!"); 
    else  e=p->data;	
}

定位操作

(1)返回地址

//时间复杂度为O(n)
LNode* LocateElem(LinkList L, ElemType e)  
{
// 查找单链表L中第一个数据域值和e相等的结点
// 若存在则返回其指针;若不存在则返回NULL
    LinkList p=L->next;	// 设置指针,初始时指向L第一个结点
    while(p&&(p->data!=e)) // 顺链向后扫描
        p = p->next; 
    return p;//返回该元素所在位置的指针
} 

(2)返回位置序号

//时间复杂度为O(n)
int LocateELem_L (LinkList L,Elemtype e) {
 //返回L中值为e数据元素的位置序号,查找失败返回0 
  LinkList p=L->next; 
  int j=1;
  while(p &&p->data!=e)  
        {p=p->next;  j++;}          		
  if(p)//找到了该元素 
    return j; 
  else //未找到该元素
    return 0;
} 

插入操作

① 顺链表向后扫描寻找第i-1个结点,并检查插入操作要求的相关参数是否合理性,如果不合理,则给出相应信息并退出运行;
② 生成一个新结点;
③ 将待插入的数据元素值赋给新结点的数据域;
④ 将第i个结点的指针赋给新结点的指针域: s->next=p->next;
⑤ 修改第i-1个结点指针域中的指针,令其指向新结点: p->next=s;
4和5的顺序不可调换!

//时间复杂度为O(n)
void ListInsert(LinkList &L,int i,ElemType e) 
 {
    // 在单链表L中第i个位置前插入值为e的结点;若插入位置不合理   则给出信息并退出运行,i的合理值为1≤i≤表长+1
    LinkList p = L;
    LNode s; 
    int j = 0;
    while(p&&(j<i-1))  { // 在L中顺链向后扫描寻找第i-1个结点
        p=p->next;  
        ++j;    
     }
    if(!p||(j>i-1))  Error(" Position Error!"); 
    s=new LNode;	// 生成新结点
    s->data=e;	// 将e赋给新结点的数据域
    s->next=p->next; // 将第i个结点指针赋给新结点指针域
    p->next=s;	// 修改第i-1个结点指针域中指针
 } 

删除操作

① 顺链表向后扫描寻找第i-1个结点,并检查插入操作要求的相关参数是否合理性,如果不合理,则给出相应信息并退出运行;
② 设置一个指针指向待删除结点,即第i个结点,为删除后释放结点空间做准备;
③ 取出待删除结点数据域的值,为返回其值做准备;
④ 删除第i个结点,即修改第i-1个结点的指针,令其指向第i+1个结点;
⑤ 释放第i个结点的存储空间。

//时间复杂度为O(n)
 void ListDelete_L(LinkList &L,int i,ElemType &e)
{
    //若删除位置不合理则给出信息退出运行,i的合理值为1≤i≤表长
    LinkList p = L; 
    int j = 0;
    while((p->next)&&(j<i-1))  
    { 
        //在L中顺链向后扫描寻找第i-1个结点
        p=p->next;  
        ++j;    
     }
    if(!(p->next)||(j>i-1)) 
        Error(" Position Error!"); 
    q = p->next;	 // 设置指针q指向被删除结点
    p->next=q->next; 	// 删除第i个结点
    delete q;	// 释放第i个结点的存储空间
} 

输出链表中所有元素

从第一个结点开始,顺链表向后扫描,依次输出表中每个结点数据域的值

//时间复杂度为O(n)
void TraverseList(LinkList L)
{
    // 依次输出单链表L中每个结点数据域的值
    LinkList p = L->next;
    while(p)  
    {
        cout<<p->data;
        p=p->next;    
     }
}

前插法方式建立单链表

① 从空表开始,重复读入数据;
② 生成新结点;将读入数据存放到新结点的数据域中;
③ 将该新结点插入到链表的前端;

//时间复杂度为O(n)
void CreateList_H(LinkList &L,int n)  
{
    //按照逆序输入n个元素值,从表尾到表头逆向建立单链表L
    L = new LNode;//先建立头结点 
    L->next=NULL; //建立一个空链表L
    LinkList p;
    for(i=n;i>0;--i)  
    {
        p=new LNode;	// 生成一个新结点
        cin>>p->data; 	//读入数据到新结点数据域
        p->next=L->next;  // 插入新结点到L中
        L->next=p;		// 头结点指针指向新结点
    }
} 

尾插法方式建立单链表

① 从空表L开始,将新结点逐个插入到链表的尾部,尾指针r指向链表的尾结点。
② 初始时,r同L均指向头结点。每读入一个数据元素则申请一个新结点,将新结点插入到尾结点后,r指向新结点

void CreateList_L(LinkList &L,int n)
{ 
      //正位序输入n个元素的值,建立带表头结点的单链表L 
      L = new LNode; 
      L->next=NULL; 	
      LinkList r=L; 	//尾指针r指向头结点 
      for(int i=0;i<n;++i)
      { 
         p=new LNode;	//生成新结点 
         cin>>p->data;   	//输入元素值 
         p->next=NULL; 
         r->next=p; //插入到表尾 
         r=p; 	//r指向新的尾结点 
      } 
}

二.循环链表

在单链表中,将最后一个结点的指针域 NULL 改为指向头结点,这样形成的链式存储结构称为单向循环链表,简称循环链表
循环链表
在循环链表中,查找第一个结点的时间是 O(1),查找最后一个结点的时间是 O(n)

链表为空的条件:L->next = L;
链表尾的条件:p->next = L;

如果在循环链表中设立尾指针 Rear 而不设头指针 Head,则对第一个结点和最后一个结点的查找时间都是 O(1)。

1

开始结点:rear->next->next
终端结点:rear

将两个循环链表合成为一个循环链表的语句

//时间复杂度为O(1)
p = B->next->next;
B->next = A->next;
A->next = p;

在这里插入图片描述

三.双向链表

在循环链表中,每个结点中除了一个存放后继结点的指针域外,再增加一个指向其前驱结点的指针域,链表中有两条方向不同的链,这样形成的链式存储结构称为双向循环链表,简称双向链表。
3emm
链表样式:
hh

  • 在双向链表第一个结点之前附加一个同结构的结点,称为头结点。其数据域可不存储任何信息,也可存储诸如线性表长度等类的附加信息。
  • 其前向指针域存储指向最后一个结点的指针,后向指针域存储指向第一个结点的指针。
  • 指向头结点的指针称为头指针。

结构体形式

typedef struct DuLNode  
{ 
    ElemType data;// 数据域
    struct DuLNode *prior;	// 前向指针域
    struct DuLNode *next; // 后向指针域
} DuLNode; 
typedef DuLNode *DuLinkList; 

空循环的双向链表
在这里插入图片描述

判断条件:L->next = L

插入结点操作

首先顺链向后扫描寻找第i个结点,检查插入参数i是否合理:

  • 如果不合理,则给出相应信息并退出运行
  • 如果合理,则生成一个新结点,并将待插数据元素值赋给其数据域
    并按照如下步骤完成插入:
    step1.
    在这里插入图片描述

s->prior = p->prior;

step2.
在这里插入图片描述

p->prior->next = s;

step3.在这里插入图片描述

s->next = p;

step4.
在这里插入图片描述

p->prior = s;

详细代码如下:

//时间复杂度为O(n)
//将元素值为e的结点插入到第i个结点之前
DuLNode* GetElemP_DuL(DuLinkList L,int i) {
    // 1≤i≤表长+1;若表中不存在该结点,则返回NULL
    DuLinkList p = L->next;  
    int j=1;
    while((p!=L)&&(j<i))  
    { // 在L中顺链向后扫描寻找第i个结点
        p=p->next;
        ++j;    
     }
 if(((p==L)&&(j!=i))||(j>i))  return NULL;
    return p;	// 返回第i个结点指针
} 


void ListInsert_DuL(DuLinkList &L,int i,ElemType e) 
{
    //在双向链表L中第i个结点之前插入值为e的结点
    //若i值不不合理则给出相应信息并退出运行
    p=GetElemP_DuL(L,i); //确定双向链表L中第i个结点的指针p
    if(p==NULL)  Error(" Position Error!"); 
    s=new DuLNode;
    s->data=e; 
    s->prior=p->prior; 	// ①设新结点前向指针指向p结点前驱
    p->prior->next=s;	// ②修改p结点前驱的后向指针
    s->next=p;		// ③设新结点后向指针指向p结点
    p->prior=s;		// ④修改p结点的前向指针
} 

删除结点

首先顺链向后扫描寻找第i个结点,检查删除参数i是否合理,如果不合理,则给出相应信息并退出运行;如果合理,则用e返回第i个结点数据域值;然后按照如下步骤完成删除:
① 修改第i-1个结点的后向指针,令其指向第i+1个结点;
② 修改第i+1个结点的前向指针,令其指向第i-1个结点;
③ 释放第i个结点的存储空间。
在这里插入图片描述

p->prior->next = p->next;
p->next->prior = p->prior;
delete p;

代码如下:

//时间复杂度为O(n)
 DuLNode *GetElemP_DuL(DuLinkList L,int i) 
{
    //与上面相比换了一种写法 两者都可以
    //返回双向链表L中第i个结点指针,1≤i≤表长;若表中不存在该结    点,则返回NULL
    DuLinkList p=L;  
    int j=0;
    while((p->next!=L)&&(j<i-1))  
    {// 在L中顺链向后扫描寻找  第i个结点
        p=p->next;
        ++j;  
    }
    if((p->next==L)||(j>i-1))  return NULL;
    return p->next;	//返回第i个结点指针
 } 


void ListDelete_DuL(DuLinkList &L,int i,ElemType &e) {// 用e返回双向链表L第i个结点数据域值并删除该结点
// 若i值不合理则给出相应信息并退出运行
    p=GetElemP_DuL(L,i);//确定双向链表L中第i个结点指针p
    if(p==NULL)  Error(" Position Error!"); 
    e=p->data;	// 取出第i个结点数据域值
    p->prior->next=p->next; // ①修改第i-1个结点后向指针
    p->next->prior=p->prior; // ②修改第i+1个结点前向指针
    delete p; 		// ③释放第i个结点空间
} 

四.静态链表

数组中一个分量表示一个结点,同时使用游标(cur)代替单链表中的指针,存储指示其后继的相对地址(最后一个分量游标域值为 0),这种用数组描述的链表称为静态链表。
在这里插入图片描述
数组中第 0 个分量可看成头结点:
其数据域可不存储任何信息,也可存储如线性表长度等类的信息;其游标域指示静态链表第一个结点。

例:在下面的静态链表中,先将“Shi”插入到“Li”和“Zhou”之间,再删除“Zheng”
在这里插入图片描述

结构体类型

#define List_Size 100// 静态链表大小
typedef struct 
{ 
    ElemType  data;		// 数据域
    int	cur;	// 游标,指示结点在数组中相对位置
} component;
typedef component SlinkList[List_Size+1]; 

在静态链表中实现线性表的操作和单链表相似,以整型游标i代替动态指针p,i=S[i].cur的操作即为指针后移(类似于单链表操作中的p=p->next)

在静态链表中查找第一个值为e的元素

如果找到,则返回其在S中的相对位置,否则返回0

//时间复杂度为O(n)
int LocateElem_SL(SlinkList S,ElemType e)  {
// 在静态链表S中查找第一个值为e的元素:如果找到,则返回它在S中的位置;否则返回0
    int i=S[0].cur;	// 令i指示S的第一个结点
    while(i&&(S[i].data!=e))	//S中顺链向后查找
        i=S[i].cur;	
    return i;
}

五.顺序表

顺序表和链表的区别

在这里插入图片描述
线性结构的基本特征是:

  • 在数据元素非空有限集合中:有且只有一个“第一个元素”;有且只有一个“最后一个元素”;
  • 除第一个元素外,其他元素都有唯一的直接前驱;除最后一个元素之外,其他元素都有唯一的直接后继。

线性表中第 n 个元素 an 的起始地址为:loc(an)=loc(a1)+(n-1)d (1≤i≤n)

在这里插入图片描述

结构体定义

#define LIST_INIT_SIZE	100// 线性表存储空间的初始分配量
#define LIST_INCREMENT  10// 线性表存储空间的分配增补量
typedef struct  {
    ElemType	*elem;		// 线性表存储空间基地址
    int  length;		// 线性表当前长度
    int  listsize;	// 当前分配的存储容量(以ElemType为单位)
} SqList; 

初始化操作

① 按照需要为线性表分配一个预定义大小的存储区域LIST_INIT_SIZE,即顺序表的最大容量,如果存储分配失败,则给出错误信息;
② 设置线性表的长度为0;
③ 设置线性表的当前存储容量为顺序表的最大容量;

//时间复杂度为O(1)
void InitList_Sq(SqList &L)  
{
    //构造一个最大容量为LIST_INIT_SIZE的顺序表L
    L.elem=new ElemType[LIST_INIT_SIZE];
    if(!L.elem)  Error(" Overflow!");		// 存储分配失败
    L.length=0;    
    L.listsize=LIST_INIT_SIZE;
}
void Error(char *s) 
{
    //出错信息处理函数,用于处理异常情况
    cout<<s<<endl;
    exit(1);
}

销毁操作

① 释放线性表中数据元素所占用的存储空间
② 设置线性表的长度为0
③ 设置线性表存储容量为0

//时间复杂度为O(1)
void DestroyList_Sq(SqList &L)  
{
    // 释放顺序表L中元素所占用的存储空间
    delete []L.elem;
    L.length=0;
    L.listsize=0;
}

清空操作

重新设置线性表为空表,即令表的长度为0,但不释放掉该表所占用的存储空间

//时间复杂度为O(1)
void ClearList_Sq(SqList &L)  
{
   // 重置顺序表L为空表
    L.length=0;
}

求表长

直接返回顺序表中的长度域值

//时间复杂度为O(1)
int ListLength_Sq(SqList L)  
{
   // 返回顺序表L的长度
    return L.length;
} 

取值操作

① 判断取数据元素值操作所要求的相关参数是否合理,如果不合理,则给出错误信息
② 返回线性表中待取数据元素的值

//时间复杂度为O(1)
//用e返回顺序表L中第i个元素值(1≤i≤L.length)
//若参数不合理则给出相应信息并退出运行
void GetElem_Sq(SqList L,int i,ElemType &e)  
{
    if((i<1)||(i>L.Length))	// 取元素值的参数不合理
        Error(" Position Error!");
    e=L.elem[i-1];
}

查找操作

① 从顺序表的第一个数据元素起,依次和给定的数据元素值进行比较,如果存在与其值相等的数据元素,则返回第1个相等元素的位序
② 如果查遍整个顺序表都没有找到与其值相等的数据元素,则返回为0

//时间复杂度为O(n),ASL = (n+1)/2
int LocateElem_Sq(SqList L,ElemType e)  
{
   for(i=0;i<L.length;i++)
      if(L.elem[i]==e) return (i+1);//查找成功,返回序号
   return 0} 

插入操作

① 检查插入操作要求的相关参数是否合理,若不合理,则给出错误信息;
② 判断当前存储空间是否已满,如果已满,则需要系统增加空间;
③ 把顺序表中原来第n至第i个数据元素依次往后移动一个位置;
④ 把新数据元素插入在顺序表的第i个位置上;
⑤ 表长加1

//时间复杂度为O(n)
void ListInsert_Sq(SqList &L,int i,ElemType e)  
{
    //在顺序表L中第i个位置前插入元素e;若插入位置不合理则给出 相应信息并退出运行
    //i的合理值为1≤i≤L.length+1
    SqList q;
    if((i<1)||(i>L.length+1))	// 插入元素的参数不合理
        Error(" Position Error!"); 
    if(L.length>=LIST_INIT_SIZE)	// 若当前存储空间已满,则增加空间
        Increment(L);
    q=&(L.elem[i-1]);			// 令指针q指向插入位置
    for(p=&(L.elem[L.length–1]);p>=q;--p)
        *(p+1)=*p;			// 依次后移元素
    *q=e;				// 在L的第i个位置中插入e
    ++L.length;			// 修正L的长度,令其增1
} 

void Increment(SqList &L) 
 {
    // 为顺序表L扩充LIST_INCREMENT个数据元素的空间
    newlist=new ElemType[L.listsize+LIST_INCREMENT];
					//增加LIST_INCREMENT个存储分配
    if(!newlist)  Error("Overflow!");	//存储分配失败
    for(i=0;i<L.length;i++)
        newlist[i]=L.elem[i];	//腾挪原空间中的数据到newlist中
    delete []L.elem;	//释放元素所占的原空间L.elem
    L.elem=newlist;	    // 移交空间首地址
    L.listsize+=LIST_INCREMENT;	// 修改扩充后顺序表的最大空间
}

稍微简单版的程序:

//时间复杂度为O(n)
void ListInsert(SqList &L,int i,ElemType e)
{
     //在顺序表L中第i个位置插入新的元素e,i值的合法范围是1<=i<=L.length+1
     if((i<1)||(i>L.length+1)) return ERROR;
     if(L.length==MAXSIZE) return ERROR;//当前存储空间已满
     for(j=L.length-1;j>=i-1;j--)
             L.elem[j+1]=L.elem[j];
     L.elem[i-1] = e;
     L.length++;
     return OK;
}  

删除元素

① 检查删除操作要求的相关参数是否合理,若不合理,则给出错误信息
② 将待删除数据元素的值赋给变量
③ 把顺序表中原来第i+1至第n个数据元素依次向前移一个位置
④ 修正顺序表的长度。

//时间复杂度为O(n)
void ListDelete_Sq(SqList &L,int i,ElemType &e)  
{
// 删除顺序表L中第i个元素并用e返回其值;若插入位置不合理则给出信息并退出运行
// i的合理值为1≤i≤L.length
    if((i<1)||(i>L.length))		// 删除元素的参数不合理
        Error(" Position Error!"); 		
   for(j=i;j<=L.length-1;j++)
        L.elem[j-1] = L.elem[j];
   --L.length;
   return OK;
} 

输出所有元素

当顺序表L非空时,依次输出L中的所有数据元素

//时间复杂度为O(n)
void TraverseList_Sq(SqList L)  
{
    // 依次输出顺序表L中的每个数据元素
    if(L.length!=0) 
    {
        i=1;				// 指示位序,初值为1
        p=L.elem;			// 指向L第i个元素,初始指向首元素
        while(i<=L.length) 
         {		// 依次输出L中的元素
            cout<<*p++;
            i++;
        } 
    }
}

六.线性表的运用

第二弹:数据结构学习笔记(二)——栈与队列

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小菲601

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值