数据结构篇(二):

数据结构篇(二):

​   这里我们直接开讲。

1.在开始本章内容之前,我们需要搞懂如下概念:

什么是线性表?

​   由同类型数据元素构成有序序列的线性结构,

​   表中的元素个数称为线性表的长度。

​   线性表没有元素时为空表。

​   表的起始位置为表头,结束位置为表尾。

线性表的基本操作有:

​   初始化一个空的线性表

​   根据序K,找到对应元素

​   在线性表中找到元素第一次出现的位置

​   在序列i前插入一个元素

​   删除指定位序的元素

​   返回线性表长度

什么是链表

​   有一系列结点(地址)组成,通过结点间的相互连接,形成的链式结构,叫做链表。

其中结点包括两个部分:

​   (1)存储数据元素的数据域(内存空间)

​   (2)存储指向下一个结点地址的指针域。

2.顺序存储结构直接表示

在这里插入图片描述

​   比如我们有上图这样一个一元多项式,那么我们应该如何用代码表示它呢?

   首先我们要抓住多项式的三个关键要素:项数、系数、指数

​   用顺序存储结构表示的话,我们可用a[i]表示x^i的系数,i表示X的指数。

举个例子:
          f(x) = 4x -3x^2+1
在这里插入图片描述

​   图示中,我们用下标i = 0表示x^0的系数,下标i = 2表示x^2的系数,以此类推。

​   但是这个方法虽然简单,但是当x的指数越大时,这个方法所需要的数组规模也就越大,空间复杂度就会越高,所以这个方法不适合大规模的计算。

​   但是对于上述问题,请大家思考,我们有必要将数组里面的0项等无用项都表示出来浪费空间吗?我们该如何改进?

​   结构数组的方法:
在这里插入图片描述

​   就像上图中,此时我们只需要表示非零项,就无须考虑那么多无用项了。

​   同理我们也可用于表示两个多项式的相加:

在这里插入图片描述

​   如上图,我们有两个多项式P1、P2,那我们接下来该怎么操作呢?
在这里插入图片描述

​   先将P1、P2的第一项做对比,显然我们看指数,19>12所以,我们指数大的那一项排在前面,做为和结果的第一项输出:

在这里插入图片描述

再接着:
在这里插入图片描述

​   比较P1第一项和P2第二项的指数大小,同样的,我们将指数大的一个做为输出,补在第一次输出的后面:

在这里插入图片描述

以此类推,最终结果为:

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

​   显然,这样的表示方法又省空间又省时间。

​   但是我们实现上述步骤,还有一种更简单有效的方法——链表。

3.链表

链表结构如下:

typedef struct PolyNode *Polynomial;
struct PolyNode
{
    int coef;  //系数
    int expon;	//指数
    Polynomial link;//指向下一个节点
	}

​   对于前面的两个多项式,用链表表示如下:

在这里插入图片描述

​   怎么样?是不是比之前的方法更简单明了。

4.线性表实现多项式相加

1.定义线性表的结构:

typedef struct LNode *List;
struct LNode
{
    ElementType Data[Maxsize]; 
    int Last;
};
struct LNode L;
List PtrL;

​   线性表的长度为L.Last+1或L->Last+1

​   ElementType默认int型,可修改。

2.初始化线性表

List MakeEmpty()
{
    List PtrL;
    PtrL = (List)malloc(sizeof(struct LNode)) //分配节点空间
    PtrL->last = -1;  //长度
    return Ptrl;
}

3.查找

int Find(ElementType X,List Ptrl)
{
    int i = 0;
    while(i<=Ptrl->Last&&Ptrl->Data[i]!=X)
        i++;
    if(i>Ptrl->Last) return -1;		//没找到返回-1
    else return i;		//找到返回i
}

4.插入

​   想要在线性表的i-1位置插入某个元素时,我们需要先将i-1位置后方的所有元素后移一位,再进行插入。

​   注意:此时要从最后一个开始往后挪,具体道理大家想想都懂。

Void Insert(Element x,int i,List Ptrl)
{
    int j = 0;
    if(Ptrl->Last == Maxsize-1)		//判断线性表是否满
    {
        printf("表满,不可加入");
        return;
    }
    if(i<0||i>Ptrl->last+2<i)		//判断i是否超出线性表
    {
        printf("输入不合法")return;
    }
    for(j = Ptrl->Last;j>=i-1;j--)
    {
        Ptrl->Data[j+1] = Ptrl->Data[j];  
    }
    Ptrl->Data[i-1] = X;
    Ptrl->Last++;
  	return;
}

5.删除

​ 先删除,再移动后面的元素。

void Delete( int i, List PtrL )
{
    int j;
    if(i<1||i>Ptrl->Last+1)	//检查空表和合法性
    {
        printf("不存在第%d个元素",i);
        return;
    }
    for(j = i;j<=Ptrl->Last;j++)
    {
        Ptrl->Data[j-1] = Ptrl->Data[j];
    }
    Ptrl->Last--;		//元素个数减一
    return;
}

6.链表操作的具体流程:

1.定义结点
typedef struct LNode*List;
struct LNode
{
    ElementType Data;
    List Nest;
}
Struct Lnode L;
List Ptrl;
2.求表长
int Length(List Ptrl)
{
    List P = Ptrl;	//指向第一个结点
    int j = 0;
    while(P)
    {
        P = P->Nest;  //进入下一个结点
        j++;		  //结点数加一
    }
    return j++;
}
3.查找

按序号:

List FindKth(int K,List Ptrl)
{
    List P = Ptrl;
    int i = 1;
    while(P!=NULL&&i<k)
    {
        P = P->Next;	//逐个查找
        i++;
    }
    if(i==k) return P; //找到返回
    else return NULL;  //没找到返回空
}

按值:

List Findth(int x,List Ptrl)
{
    List P = Ptrl;
    while(P!=Null&&P->data!=x)	//判断是否为空和相等
    {
        P = P->Next;
    }
    return P;
}
4.插入

在这里插入图片描述

​   因为链表的内存是动态的,所以我们在添加一个结点的同时,还要为它创建一个空间,用malloc实现。

List insert(ElementType x,int i,List Ptrl)
{
    List s,p;
    if(i == 1)  //如果加在指针头部
    {
        s = (list)malloc(sizeof(struct LNode)); //分配空间
        s->Data = x;
        s->Next = Ptrl;
        return s;
    }
    p = Findth(i-1,Ptrl);		//找到对应的结点
    if(p == NULL)
    {
        printf("参数出错");
        return NULL;
    }else
    {
        s = (list)malloc(sizeof(struct LNode));
        s->Data = x;
        s->Nest = p->Next;	
        p->Nest = s;
        return Ptrl;
        /*
        将p指向的的下个结点改编成s,s指向的下个结点转变成p原本指向的那个结点
        */
    }
}
5.删除操作

在这里插入图片描述

  比如你想删除第2个结点,你需要将1结点指向的下个结点,指向3.

List Delete(int i,List PtrL)
{
    List p,s;
    if(i == 1)
    {
        s = PtrL;
        if(PtrL!=NULL) PtrL = PtrL->Next;
        else return NULL;
        free(s);
        return PtrL;
    }
    /*
    这里要注意删除的结点为第一个结点时,应该判断后续是否有结点,如果没有结点则直接释放空间,如果有结点直接转向下一个结点
    */
    p = FindKth(i-1,PtrL);
    if(p == NULL)
    {
        printf("第%d个结点不存在",i-1);
        return NULL;
    }else if(p->Next == NULL)
    {
        printf("第%d个结点不存在",i);
       	return NULL;
    }else
    {
        s = P->Next;
        p->Next = s->Next;
        free(s);
        return PtrL;
    }
}

7.广义表与多重链表

​   除了遇到一元多项式的问题,我们还经常遇到多元问题,如下:
  在这里插入图片描述

​   此时的变量就不只是x了,而是包含x、y的二元多项式,这种情况我们还能用解决一元的方式来解决吗?

​   当然可以

我们可以将上式转换如下:

在这里插入图片描述

​   这样形成x的“一元方程”,随后将系数转换,不再是常量而是应该是一个指针,指向y形成的“一元方程”做为x的系数。

在这里插入图片描述

​   此时就可以用解决一元的方式解决二元的问题。

typedef struct GNode *GList;
struct GNode
{
    int Tag;
    union
    {
        ElementType Data;
        GList SubList;
    }URegion;
    /*
    这里注意union可存放不同类型的数据
    */
    Glist Next;
};

        在这里插入图片描述

  其中,Tag是标志域,0表示结点是单元数,1表示是广义表。

多重链表:

在这里插入图片描述

​   如图,如果用二维数组表示矩阵,则会造成很大的浪费,0项很多,所以我们这里只表示非零项,并且每个结点通过两个指针域把行列串起来,有行指针和列指针。
在这里插入图片描述

​   由图,两个红色的循环结点,而红框中的结点,即属于某一行也属于某一列,则称为十字链表,黄色的是列循环链表的头结点,蓝色是行循环列表的头结点,紫色框中的结点作用,相当于Term,是稀疏矩阵的入口,其中4代表行数,5代表列数,7代表7个非零项,通过这个指针可以找到所有行列的头结点。

8.堆栈

​   首先让我们来讲讲什么叫后缀表达式:

​   即运算符号在两个数的后面,我们平常用到的表达式为中缀表达式,即运输符号在两个数当中

在这里插入图片描述

​   由图,第一行为中缀,后一行为后缀。

(后缀表达式的读法,从计算符号往前看)。

这里用:

         在这里插入图片描述

​ 来举个例子。

           在这里插入图片描述

​   首先,堆栈时先入后出,后入先出,

​   就比如上式中,先把计算式按+×24-3/26(上述计算式反过来,从右端输入,输入第一个数为6),一个一个元素加入堆中,再一个一个拿出来计算(例:输入/26后算出3再存回原处,再与后面33-的表达式进行计算,算出结果为0再存储回去,不断更新值),抱住后缀表达式的正确性。

           在这里插入图片描述

​ 接收到/号时:

            在这里插入图片描述

​ 接收到-号时:
            在这里插入图片描述

​   再输入后面的计算式。

堆栈中,插入数据——入栈。

​     推出数据——出栈。

数组实现:

#define Maxsize
typedef struct SNode*Stack
struct SNode
{
    ElementType Data[Maxsize];
    int Top;  //元素个数
};
(1).入栈
void Push(Stack Ptrs,ElementType item)
{
    if(Ptrs->Top>Maxsize-1)
    {
        printf("堆栈满")return;
    }else
    {
        Ptrs->Data[++(ptrs->Top)] = item;
        return;
    }
}

       在这里插入图片描述

(2)出栈
ElementType Pop(Stack Ptrs)
{
    if(ptrs->Top == -1)
    {
        printf("堆栈空");
        return ERROR;
    }else
    {
        return(Ptrs->Data[(ptrs->Top)--]);
        //出栈以后,Top减一
    }
}

​   下面让我们考虑一个问题,用一个数组实现两个堆栈,要求最大的利用数组空间,使数组有空间入栈就可以成功。

​   显然,如果只是简单对半分组,显然不能有效的利用数组空间,可能存在后一个堆栈满了,而前一个堆栈还剩许多空间的状况。

​   面对这种情况,我们就要想个比较聪明的方法,从两边往中间堆,当两个堆栈Top相遇时,则数组就满。

操作如下:

#define Maxsize
struct Dstack
{
    ElementType Data[MaxSize];
    int Top1;
    int Top2;
}s;
//其中,s.Top1 = -1     s.Top2 = Maxsize 

push操作:

void push(struct Dstack *Ptrs,ElementType item,int Tag)
{
    if(Ptrs->Top2-Ptrs->Top1 == 1)
    {
        printf("堆栈满")return;
    }
    if(Tag == 1)//判断用第几个堆栈
    {
        Ptrs->Data[++(Ptrs->Top1)] = item;
    }else
    {
        Ptrs->Data[--(Ptrs->TOP2)] = item;
    }
}

pop操作:

ElementType Pop(struct Dstack *Ptrs,int Tag)
{
    if(Tag == 1)
    {
        if(ptrs->Top1 == -1)
        {
            printf("堆栈1空");
            return NULL;
        }else return Ptrs->Data[(Ptrs->Top1)--];
    }else
    {
        if(ptrs->Top2 == Maxsize)
        {
            printf("堆栈2空");
            return NULL;
        }else
            return Ptrs->Data[(Ptrs->Top2)++];
    }
}
(3)单向链表

​   正如我们所知,堆栈也可用单向链表实现,但是由于堆栈的特点,我们的插入和删除只能在链表的一头操作,并且要在链表的头部操作,在链表的尾部操作时,进行删除操作会找不到前一个结点,所以不可以用链尾操作。

定义结点结构:

typedef struct SNode *Stack;
struct SNode
{
    ElementType Data;
    struct SNode *Next;
}

建立堆栈头结点:

Stack CreatStack()
{
	Stack S;
    S = (Stack)malloc(sizeof(struct SNode));
    S->Next = NULL;
    return S;
}

判断堆栈是否为空:

int ISEmpty(Stack S)
{
    return (S->Next == NULL);
}

push操作:

void Push(ElementType item,Stack S)
{
    Struct SNode *TmpCell;
    TmpCell = (struct SNode *)malloc(sizeof(struct SNode));
    TmpCell->Element = item;
    TmpCell->Next = S->Next;
    s->Next = TmpCell;
}

pop操作:

ElementType Pop(Stack S)
{
    struct SNode *FirstCell;
    ElementType TopElem;
    if(IsEmpty(S))
    {
        printf("堆栈空")return NULL;
    }
    else
    {
        FirstCell = S->Nest;	//去一个变量存储要删除的结点
        S->Next = FirstCell->Next;  //更改指向的结点为删除结点的Next
        TopElem = FirstCell->Element;
        free(FirstCell);
        return TopElem;
    }
}

​   因为链表空间使动态的,所以不用判断满不满。

中缀表达式:

​   这里实在打的有点累了,就说明下怎么操作吧。

在这里插入图片描述

​   如图遇到a b c d运算值时进行输出,遇到算术符号时进行堆栈,如图,先是将一个个算术符号进行堆栈存储,再逐步确认优先级,上图中,下一个输入的是“)”,则可以确定到“(”的所有算术符号都可以pop出去,如下:

在这里插入图片描述

随后输入的“/”号与“×”号的优先级采取同级左边的优先,则×号也出去,如下:

在这里插入图片描述

最后再把/号pop出去计算就好。

最终中缀转后缀操作如下:
   1.运算数:直接输出。

​    2.左括号:压入堆栈。

​    3.右括号:将栈顶的运算符弹出并输出,直到遇到左括号(出栈,不输出)。

​    4.运算符:

​    若优先级大于栈顶运算符时,则把它压栈;
​    若优先级小于等于栈顶运算符时,将栈顶运算符弹出并输出;再比较新的栈顶运算符,直到该运算符大于栈顶运算符优先级为止,然后将该运算符压栈;

​    5.若各对象处理完毕,则把堆栈中存留的运算符一并输出。

9.队列(Queue)

​   队列用通俗的话来讲就相当于我们刷身份证过动车安检,我们排只能从队伍的后方开始排,出去只能从队伍的前方出去。

​   插入和删除操作:只能在一端插入,而在另一端别除。

Queue CreatQueue( int MaxSize )://生成空队列
int IsFullQ( Queue Q, int MaxSize )://判断队列是否满
void AddQ( Queue Q, ElementType item )://将item加到队列中
int IsEmptyQ(Queue Q )://判断队列是否为空
ElementType DeleteQ( Queue Q )://将队头数据删除

​   因为队列在两头发生,所有要有一个front和一个rear

定义队列:

#define Maxsize
struct QNode
{
    ElementType Data[Maxsize]; //队列数组
    int rear; //队尾
    int front;	//队头
};
typedef struct QNode *Queue;

在这里插入图片描述

​   每次增加一次工作则,Rear++,Front你不变。删除了一个工作以后,Front++,Rear不变。

在这里插入图片描述

​   当遇到如下情况时,我们肯定会想既然是排队,那你再排到队尾去不就好了吗?

​   所以这就形成了我们所熟知的顺环队列。

在这里插入图片描述

如上就是顺环队列,其中也有不少问题让我们思考:

在这里插入图片描述

​   当遇到如上情况时,如果我们再加入一个job,就会使Rear==front跟数列为空的状态冲突了,此时我们就无法分辨队列到底是满了还是空了。

​   为什么会造成这样的错误呢?

​   因为,如果我们队列的大小为n时,队列的状态应该有n+1种,但是front和rear的差距只有n种情况,我们用n种情况去衡量n+1种状态怎么可能不出错呢?就等于给你一个标志位i,他只有0 1两种电平,你却要他表示3种状态,这样肯定会出错的。

​   那我们如何解决这一问题呢?

​   1.使用额外的标志位表示第n+1种状态,一般是记录操作时插入还是删除,如果rear = front时对应的操作时插入,则就是队列满,反之则是队列空。

​   2.只使用n-1个数组空间,比如有6个空位我只放5个位,第6位舍弃不用。

当然我们也可以用链表实现队列:

​ 那么问题来了,相比你还记得之前我们遇到的问题,堆栈时,我们用单向链表操作时要选择队头操作还是队尾操作,队列也不例外。

​   此时,队列也是跟堆栈一样也是只能用队头,不能用队尾,原理相同。

定义结点:

struct Node
{
    ElementType Data;
    struct Node *Next;
};

定义队列:

struct QNode
{
    struct Node *rear;
    struct Node *front;
};
typedef struct QNode *Queue;
Queue PtrQ;

出队操作:

ElementType DeleteQ(Queue PtrQ)
{
    struct Node *FrontCell;
    ElementType FrontElem;
    if(PtrQ->front == NULL)
    {
        printf("队伍空")return ERROR;
    }
    FrontCell = PtrQ->front;
    if(PtrQ->front==PtrQ->rear)
        PtrQ->front = PtrQ->rear = NULL;
    else
        PtrQ->front = PtrQ->front->Next;
    FrontElem = FrontCell->Data;
    free(FrontCell);
    return FrontElem;
}

  目标的坚定是性格中最必要的力量源泉之一,也是成功的利器之一。没有它,天才也会在矛盾无定的迷径中徒劳无功。
数据结构篇(一):_风声向寂的博客-CSDN博客
(2条消息) 数据结构篇(三):_风声向寂的博客-CSDN博客
数据结构篇(四):_风声向寂的博客-CSDN博客
数据结构篇(五):_风声向寂的博客-CSDN博客

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值