精华内容
下载资源
问答
  • 大话数据结构笔记:循环队列及队列的链式存储
    2020-02-16 13:41:31

    一、循环队列

    1.循环队列的顺序存储结构代码:

    #define MAXSIZE 20
    
    typedef struct
    {
    	int data[MAXSIZE];
    	int front;             //头指针
    	int rear;              //尾指针,若队列不为空,指向队列尾元素的下一个位置
    }SqQueue;
    

          队列满的条件是:
          (rear+1)%QueueSize == front;
          通用的计算队列长度公式:
          (rear-front+QueueSize)%QueueSize;

    2.循环队列初始化代码:

    //初始化一个空队列Q
    int InitQueue(SQqueue *Q)
    {
    	Q->front = 0;
    	Q->rear = 0;
    	return 1;
    }
    

    3.循环队列求队列长度代码:

    //返回Q的元素个数,也就是队列的当前长度
    int QueueLength(SqQueue Q)
    {
    	return (Q.rear-Q.front+MAXSIZE)%MAXSIZE;
    }
    

    4.循环队列入队操作代码:

    //若队列未满,则插入元素e为Q新的队尾元素
    int EnQueue(SqQueue *Q, int e)
    {
    	if((Q->rear+1)%MAXSIZE == Q->front)            //判断是否队满
    		return 0;
    	
    	Q->data[Q->rear] = e;                          //将元素e赋值给队尾
    	Q->rear = (Q->rear+1)%MAXSIZE;                 //rear指针向后移动,若到最后则转到数组头部
    
    	return 1;
    
    }
    

    5.循环队列的出队操作代码:

    //若队列不为空,则删除Q中队头元素,用e返回其值
    int DeQueue(SqQueue *Q, int *e)
    {
    	if(Q->front == Q->rear)                       //判断是否队空
    		return 0;
    
    	*e = Q->data[Q->front];                       //将队头元素赋值给e
    	Q->front = (Q->front+1)%MAXSIZE;              //将front后移一位,若到最后则转到数组头部
    }
    

    二、队列的链式存储

          队列的链式存储结构,起始就是线性表的单链表,只不过它只能尾进头出而已,我们把它简称为链队列。队头指针指向链队列的头结点,队尾指针指向终端结点。空队列时,front和rear都指向头结点。

    1.链队列结构:

    typedef struct QNode           //结点结构
    {
    	int data;
    	struct QNode *next;
    }QNode,*QueuePtr;
    
    typedef struct                 //队列的链表结构
    {
    	QueuePtr front,rear;       //队头、队尾指针
    }LinkQueue;
    

    2.入队操作:

    //插入元素e为Q的新的队尾元素
    int EnQueue(LinkQueue *Q, int e)
    {
    	QueuePtr s = (QueuePtr)malloc(sizeof(QNode));
    	if(!s)                     //存储分配失败
    		exit(-1);
    
    	s->data = e;
    	s->next = NULL;
    	Q->rear->next = s;         //新结点作为原队尾结点的后继
    
    	Q->rear = s;               //新结点作为队尾
    	return 1;
    }
    

    3.出队操作:

    //若队列不空,删除Q的队头元素,用e返回其值,并返回1,否则返回0
    int DeQueue(LinkQueue *Q, int *e)
    {
    	QueuePtr p;
    	if(Q->front == Q->rear)
    		return 0;
    
    	p = Q->front->next;          //将欲删除的队头结点暂存给p
    	*e = p->data;                //将欲删除的队头结点的值赋值给e
    	Q->front->next = p->next;    //将原队头结点后继赋值给头结点后继
    
    	if(Q->rear == p)             //若队头是队尾,则删除后将rear指向头结点(即删除后队列为空)
    		Q->rear = Q->front;
    
    	free(p);
    
    	return 1;
    }
    

          在可以确定队列长度最大值的情况下,建议用循环队列,如果无法预估队列的长度,就用链队列。

    更多相关内容
  • 五、队列的定义定义:队列(queue):队列是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。队列是一种先进先出(First In First Out)的线性表,简称FIFO。允许插入操作的一端称为队尾,允许删除操作...
    五、队列的定义
    定义:队列(queue):队列是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。
    队列是一种先进先出(First In First Out)的线性表,简称FIFO。允许插入操作的一端称为队尾,允许删除操作的一端称为队头。
    队列与现实生活中的排队机制很像,排在队头的出队,而想入队则只能从队尾开始。
    ===============================================================================
    队列:
    顺序存储结构:(循环队列)

    #define N 10
    typedef int data_t;
    typedef struct
    {
    data_t data[N]; //数据存储
    int length; //不能用,注意想想方式
    }Node;

    ===============================================================================
    六、循环队列
    线性表有顺序存储和链式存储两种结构,队列作为特殊的线性表,自然也有顺序队列和链式队列两种形式。
    但是,队列的顺序存储却有很大的缺陷。如果我们要建立元素个数为n的队列,则需要建立一个数组长度不小于n的数组,数组下标为0的为队头,当最大下标的为队尾。若有元素要入队,则只需将其存储在第n+1个位置即可。
    而若想出队,则删除了下标为0的元素后,所有在其后的元素都需要向前移动一格,即保持下标为0的元素为队头。但这样做显然浪费了大量时间。
    解决该问题的方法就是不再限制下标为0的元素为队头,每次出队后,队头自动变成当前数组下标最小的元素即可。这样就无需所有元素向前移动。
    但是,若如此做,则会造成大量的已出队的元素的存储空间浪费。而且,若此时入队元素已经大于n,则我们需要更大的存储空间才行,但队头位置有大量空间未利用,空间浪费严重。
    解决以上问题的方法就是如果后面满了,则我们就从头开始,也就是将队列做成头尾相接的循环。我们把这种头尾相接的顺序存储结构的队列称为循环队列。
    这样,我们就需要两个指示其队头(front)和队尾(rear)的下标变量。
    typedef int data_t;
    typedef struct
    {
    data_t data[MAXSIZE];
    int front; 头位置下标
    int rear; 尾位置下标
    }SqQueue;

    循环队列的操作:
    1、创建空的循环队列(CreateSqQueue)
    2、判断是否为空队列(SqQueueEmpty)
    3、判断是否为满队列(SqQueueFull)
    4、新数据元素入队(EnQueue)
    5、数据元素出去(OutQueue)
    6、求队长(SqQueueLenght)
    7、遍历循环队列(VisitSqQueue)

    当front==rear时,此时队头和队尾重合,则该队列为空队列;当(rear+1)%QueueSize==front时,此时队尾的下个位置就是队头,则该队列为满队列。注意rear的位置不是队尾元素的位置,而是队尾元素的下一个位置,即当队列满时,队列中还有一个空闲存储空间,但我们规定该状态下就是满队列。
    那么,定义好队列的的队头和队尾位置,我们来考虑怎样计算队列长度。
    当rear>front时,表示队尾在队头右边,此时队列长度为rear-front;当rear<front时,表示队尾在队友左边,此时计算队列长度应分成两部分,即rear一部分,QueueSize-front一部分,总体长度为rear-front+QueueSize。
    通用计算队列长度的公式是
    length=(rear-front+QueueSize)%QueueSize
    1、判断队是否为空EmptyQueue
    //代码见附录
    2、判断队是否为满FullQueue
    //代码见附录
    3、入队操作EnQueue
    //代码见附录
    4、出队操作DeQueue
    //代码见附录
    从循环队列的特性我们可以看出,循环队列虽然操作较为简单,但是由于队列定长,因此会出现数据溢出问题。这时我们需要考虑使用队列的链式存储结构。
    ===============================================================================
    七、队列的链式存储结构及实现
    队列的链式存储结构本质上是从单链表演化而来的。将单链表改造成链式队列,如果将头结点做为队头,最后一个节点做为队尾,则该队列的出队操作方便,而入队操作较慢;反之,如果将头结点做为队尾,最后一个节点做为队头,则该队列的入队操作方便,而出队操作较慢。
    那么,能否将单链表稍加改进,使得该链式队列的入队操作和出队操作一样方便呢?
    答案是可以的,只需要改进头结点。将“头结点存储一个next指针”改为“头结点存储两个指针front和rear”,front指针指向队头,rear指针指向队尾。这样我们进行出队/入队操作时,只需要访问这两个指针就能快速地找到队头/队尾。
    1、队列的链式存储结构定义
    将单链表的头结点稍加改造
    typedef int data_t;
    typedef struct node_t//定义单链表
    {
    data_t data;
    struct node_t *next;
    } linknode_t, *linklist_t;
    typedef struct//定义链式队列
    {
    linklist_t front, rear;
    } linkqueue_t;

    链式队列的操作:
    申请的头指针为lq,lq->front == lq->rear
    1、创建空链式队列(CreateLinkQueue)
    2、判断是否为空(LinkQueueEmpty) rear == front != NULL
    3、清空链式队列(ClearLinkQueue)
    4、求链式队列的长度(LinkQueueLength)
    5、新数据元素入队(EnLinkQueue) rear指向最后元素
    6、数据元素出队(DeLinkQueue)
    7、遍历整个链式队列数据元素(PrintLinkQueue)

    2、判定链式队列是否为空
    由于单链表的属性,链式队列几乎不会出现“队列已满”的情况,因此不考虑判定链式队列是否已满的操作。
    判定链式队列是否为空,只需要判定队列的front指针是否为空即可。
    //代码见附录
    3、队列的链式存储结构——入队操作
    入队操作其实就是在链表尾部插入节点。新来的数据节点附在当前rear节点之后,并将rear节点指向该节点即可。
    //代码见附录
    4、队列的链式存储结构——出队操作
    出队操作就是将链表的头结点的后继节点出队,并将其之后的节点设置为头结点的后继节点。若链表除头结点外仅剩一个元素,则需将rear指向头结点。
    //代码见附录

    对于循环队列和链式队列的比较,二者从时间复杂度上来说都是O(1),不过链式队列需要花费额外的申请节点的时间,而且链式队列删除节点也需要一些时间。从空间上来说,循环队列有固定长度,就意味着循环队列有其存储上限,因此也就存在元素溢出以及空间浪费等问题,而链式队列则不存在这些问题。
    总体来说,若可以大致确定元素个数的情况下,推荐使用循环队列;若无法事先预知元素个数,则应使用链式队列。
    ===============================================================================
    ======================================================================
    展开全文
  • 循环队列的顺序存储 #include<stdio.h> #include<stdlib.h> #define MaxSize 10 typedef int QElemType; typedef struct{ //对循环队列的结点的结构体进行定义 QElemType data[MaxSize]; int front;...

    循环队列的顺序存储

    #include<stdio.h>
    #include<stdlib.h>
    #define MaxSize 10
    
    typedef int QElemType;
    
    typedef struct{     //对循环队列的结点的结构体进行定义
        QElemType data[MaxSize];
        int front;      //头指针指向队首元素
        int rear;       //尾指针指向队尾元素的下一个位置
    }SqQueue;
    
    int InitQueue(SqQueue *Q){//初始化队列
        Q->front=0;
        Q->rear=0;
        return 1;
    }
    
    int Length(SqQueue *Q){//求队列长度
        int len=(Q->rear-Q->front+MaxSize)%MaxSize;
        return len;
    }
    
    int EnQueue(SqQueue *Q,QElemType e){//入队列
        if(Length(Q)+1==MaxSize){
            printf("队列已满,数值%d无法进入\n",e);
            return 0;
        }
        Q->data[Q->rear]=e;
        Q->rear=(Q->rear+1)%MaxSize;
        return 1;
    }
    
    int DeQueue(SqQueue *Q,QElemType *e){//出队列
        if(Q->rear==Q->front){
            printf("队列已空,无法出队列\n");
            return 0;
        }
        *e=Q->data[Q->front];
        Q->front=(Q->front+1)%MaxSize;
        return 1;
    }
    
    void Print(SqQueue *Q){
        int i=0;
        printf("\n");
        if(Q->front>Q->rear){
            while(Q->front+i==MaxSize){
                printf("%d  ",Q->data[Q->front+i]);
                i++;
            }
            i=0;
            while(i!=Q->rear){
                printf("%d  ",Q->data[i]);
                i++;
            }
        }
        else{
            i=Q->front;
            while(i!=Q->rear){
                printf("%d  ",Q->data[i]);
                i++;
            }
        }
        printf("\n");
    }
    
    int main(){
        
        
        SqQueue Q;
        InitQueue(&Q);
        do{
    		printf("   请选择要进行的操作:          \n");
    		printf("   1 创建一个循环队列   2 入队   3 出队    4 打印   0 退出       \n");
    		int select;
    		scanf("%d", &select);
    		if(select == 0)
    			break;
    		switch(select){
    		case 0:
    			break;
    		case 1:
    			printf("要创建的循环队列长度为:");
    			int n;//要创建的循环队列长度
        		scanf("%d",&n);
        		printf("请依次输入元素:");
        		int an; 
        		for(int i=0;i<n;i++){//入队列
        	
        			scanf("%d",&an);
            		EnQueue(&Q,an);
        		}
        		printf("队列长度为:%d \n",Length(&Q));
    			break;
    		case 2:
    			printf("请输入要入队的一个元素:");
    			scanf("%d",&an);
    			EnQueue(&Q,an);
    			break;
    		case 3:
    			int num;
    			int e;
    			printf("输入要删除的元素个数(从队首进行删除)\n");
        		scanf("%d",&num);
        		for(int i=0;i<num;i++){
            	if(!DeQueue(&Q,&e))
                	break;
            	printf("删除了一个元素%d,此时队列为:",e);
            	Print(&Q);
            	printf("\n");
        		}
        		break;
        	case 4:
        		Print(&Q);
        		break;
    		default:
    			printf("你进行了误操作,请重新选择\n:");
    			break;
    		}
    	}
    	while(1);
    	return 0;
    }
    

    循环队列的链式存储

    #include<iostream>
    #include<stdLib.h>
    using namespace std;
    
    //定义链队列 
    typedef struct Haha{
    	int data;
    	struct Haha *next;	
    }Haha, *que;
    
    typedef struct{
    	que front;
    	que rear;
    }link;
    
    //对链队列初始化
    void Init(link *q){
    	q->front = q->rear = (que)malloc(sizeof(Haha));
    	if(!(q->front)) 
    		exit(1);
    	q->front->next = 0;
    } 
    
    //插入新元素
    void En(link *q, int e){
    	que p;
    	p = (que)malloc(sizeof(Haha));
    	if(!p)    exit(1);
    	p->data = e;
    	p->next = 0;
    	q->rear->next = p;
    	q->rear = p;
    }
    
    //删除队头元素
    int de(link *q){
    	Haha *p;
    	int e;
    	if(q->front==q->rear)  return -1;
    	p = q->front->next;
    	e=p->data;
    	q->front->next = p->next;
    	if(q->rear == p)
    		q->rear = q->front;
    	free(p);
    	cout << e << endl; 
    	return 0;
    }
    
    //输出队列元素
    void out(link *q){
    	Haha *r=q->front->next;
    	while(r){
    		cout << r->data<<' ';
    		r = r->next;
    	} 
    	cout << endl;
    } 
    
    //求队列长度
    int len(link *q){
    	Haha *r=q->front->next;
    	int s = 0;
    	while(r){
    		r=r->next;
    		s++;
    	} 
    	return s;
    } 
    
    int main(){
    	link Q;
    	Init(&Q);
    	do{
    		printf("   请选择要进行的操作:          \n");
    		printf("   1 创建一个链队列   2 入队   3 出队    4 打印   0 退出       \n");
    		int select;
    		scanf("%d", &select);
    		if(select == 0)
    			break;
    		switch(select){
    		case 0:
    			break;
    		case 1:
    			printf("要创建的循环队列长度为:");
    			int n;//要创建的循环队列长度
        		scanf("%d",&n);
        		printf("请依次输入元素:");
        		int an; 
        		for(int i=0;i<n;i++){//入队列
        	
        			scanf("%d",&an);
            		En(&Q,an);
        		}
        		printf("队列长度为:%d \n",len(&Q));
    			break;
    		case 2:
    			printf("请输入要入队的一个元素:");
    			scanf("%d",&an);
    			En(&Q,an);
    			break;
    		case 3:
    			printf("队首删除了一个元素\n");
    
            	if(!de(&Q))
                	break;
            	printf("删除了一个元素,此时队列为:");
            	out(&Q);
            	printf("\n");
        
        		break;
        	case 4:
        		out(&Q);
        		break;
    		default:
    			printf("你进行了误操作,请重新选择\n:");
    			break;
    		}
    	}
    	while(1);
    	return 0;
    
    }
    
    展开全文
  • 队列链式存储结构

    2022-04-24 19:44:42
    队列链式存储结构:只能尾进头出的单链表也叫链队列。 与顺序存储结构一样有front指针和rear指针,只不过front指向头结点,rear指向最后一个结点。 当front指针和rear指针都指向头结点时,链表为空。 链...

        队列的链式存储结构:只能尾进头出的单链表也叫链队列。(队列的特点:先进先出)

        与顺序存储结构一样有front指针和rear指针,只不过front指向头结点,rear指向最后一个结点。

    watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5pel5L2c5pyI5oGv,size_20,color_FFFFFF,t_70,g_se,x_16

     当front指针和rear指针都指向头结点时,链表为空。

    watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5pel5L2c5pyI5oGv,size_20,color_FFFFFF,t_70,g_se,x_16

     

    链队列的结构代码:

    typedef struct QNode

    {

        type data;

        struct QNode *next;

    }QNode , *QueuePtr;

     

    typedef struct

    {

        QueuePtr front,rear;    

    }LinkQueue;

     

    入队操作:插入元素s到队尾

    第一步:让s指向空

    第二步:把s接到rear的下一位。

    第三步:让rear指向s

    watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5pel5L2c5pyI5oGv,size_20,color_FFFFFF,t_70,g_se,x_16

    入队结构代码:

    Status EnQueue(LinkQueue *Q , type e)

    {    // 动态分配内存

        QueuePtr s=            (QueuePtr)malloc(sizeof(QNode));

        if( !s )    // 如果内存分配失败,抛出异常

        exit(OVERFLOW);

        s->next=NULL;    // 让s的指针域指向空

        Q->rear->next=s;    // 把s接到rear后面

        Q->rear=s;    // 把s赋给rear

        return OK;

    }

     

    出队操作:让第一个结点a1出队并释放

    第一步:把a1存入结点p

    第二步:让头结点的指针域指向a2

    第三步:释放p

    若链表只有一个结点a1,则需将rear指向头结点。

    watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5pel5L2c5pyI5oGv,size_20,color_FFFFFF,t_70,g_se,x_16

     出队结构代码:

    Status DeQueue(LinkQueue *Q , type *e)

    {    

        QueuePtr p; 

        if(Q->front==Q->rear)    // 判断队列是否为空

            return ERROR;

        p=Q->front->next;    // 把队头存入p

        *e=p->data;    // 把队头的数据存入e

     // 头指针指向第二个元素

        Q->front->next=p->next;   

        if(Q->rear==p)    // 如果只有一个元素

            Q->rear=Q->front;    //rear指向头结点

        free(rear);    // 释放第一个结点

        return OK;

    }

     

    总结:

    在可以确定队列长度最大值的情况下,建议使用循环队列,若无法预估队列长度时,建议使用链队列。

     

     

    展开全文
  • 前面已经讲解过栈是什么,也用顺序存储的方式实现了栈,今天学习了链表,我们就用链式存储来实现一下栈         栈就是规定在一端进行插入和删除的线性表,而用链式存储...
  • 一、队列的基本概念 1.1 队列的定义   队列(Queue) 简称队,也是一种操作受限的线性表,只允许在表的一端进行插入,而在表的另一端进行删除。向队列中插入元素称为入队或进队。删除元素称为出队或离队。这和我们...
  • 链式存储结构中栈、队列循环链表,都是在上篇的单链表基础上进行实现的。话不多说先看链式存储的栈。 栈 先看一下的类图: 接下来是实现: public class LinkedStack<E> implements Stack<E>{ ...
  • 循环队列-链式存储结构-c语言实现

    千次阅读 2015-07-03 15:57:08
    /* 循环队列-线性表-链式结构 */ #include #include #define OK 1 #define ERROR 0typedef int Status; typedef int QElemType; //节点 定义 typedef struct QNode{ QElemType data; struct
  • 循环队列Queue–使用链式存储结构实现 数据成员 头指针:front 尾指针:rear #include #include #include <time.h>#define MAX_SIZE (10) #define RAMDOM(x) (rand() % 100) typedef int QElement;struct QNode {...
  • 队列链式存储结构,其实就是线性表的单链表,只不过只能尾进头出,简称为链队列。 将头指针指向链队列的头结点 并不是front指针直接指向第一个元素。 二.链队列实现 链队列的存储结构,这里申明两个结构体,一个...
  • 队列链式存储结构,其实就是线性表的单链表。只不过是能尾进头出,简称为链队列。为了操作上的方便,将队头指针指向链队列的头结点。而队尾指针指向终端结点。 空队列时,front 和 rear 都指向头结点。 链队列的...
  • 1.队列 1.1 队列的基本概念 队列的定义 队列(Queue)简称队,也是一种操作受限的线性表,只允许在表的一端进行插入,而在另一端进行删除。向队列中插入元素称为入队或者进队,删除元素称为出队或者离队。特点是先进...
  • 队列的实现(循环顺序存储结构+,链式存储结构+STL类模板)队列1.循环顺序存储结构2.链式存储结构3.STL类模板实现 队列 1.循环顺序存储结构 // 队列(循环队列顺序存储结构)C++.cpp : 此文件包含 "main" 函数。程序...
  • 队列的实现顺序存储的实现循环队列的顺序存储结构初始化队列队列清空判断队列是否为空队列长度取出对头元素入队出队遍历队列链式存储的实现循环队列链式存储结构初始化队列销毁队列将队列Q置空判断队列Q是否为空...
  • 前面我们实现了队列的顺序存储结构的一般功能。并用循环列表使取第一个元素的时间复杂度从O(n)降到了O(1)。一般来说,当我们已知队列的最大可能长度时,用顺序存储...并利用了函数模板实现了队列链式存储结构的实例...
  • 循环队列代码+注释运行结果 1. 队列基本概念 队列(Queue)是一种限定性线性表,它只允许在表的一端插入元素,而在另一端删除元素,具有先进先出的特点。在队列中,允许插入的一端称为队尾(Rear),允许删除的一端...
  • 队列链式存储结构,其实就是线性表的单链表,只不过它只能尾进头出而已,我们把他简称为链队列。 为了操作方便,我们将队的头指针指向链队列的头结点,而队尾指针指向终端结点。 空队列时,front和rear都指向头...
  • 1 #include "stdafx.h" 2 #include <iostream> 3 #include <exception> ... 6 //循环队列的顺序存储结构 7 8 typedef int Status; 9 const int ok = 1; 10 const int e...
  • 在单链表中我们只存储了向后的指针,到达尾标志就停止了。我们可以通过一个结点找到后结点,但是无法找到前结点了。 如果我们将尾结点的指针域指向头结点,一个单链表就形成了一个环,这种链表叫循环链表。(主要...
  • 链队是指采用链式存储结构实现的队列。通常链队用单链表来表示,如下图所示。一个链队显然需要两个分别指向队头和队尾的指针(分别称为头指针和尾指针)才能唯一确定。这里和线性表的单链表一样。为了操作方便起见,...
  • 栈;循环队列链式队列(栈实现计算器)
  • 在上次的文章中,我们实现了队列的顺序存储,也就是循环队列,既然有顺序存储,那就有链式存储。链式有个问题,究竟是需要头结点呢,还是不需要头结点呢,其实都可以,在链栈中我们使用的是没有头结点,那么在链队中...
  •      在《数据结构·队列(链式存储,C实现)》一节,讲解了使用单链表方式来实现队列的功能。本节将着重描述如何使用单链表方式来实现循环队列。                                    ...
  • 第三章 栈和队列 3.1、栈 3.1.1栈的基本概念 (1)栈(Stack)的定义 栈是只可以在一端进行插入或删除操作的线性表。栈本质上是一种线性表,但这种线性表限定只能在某一端进行插入和删除操作。 栈顶(Top):允许...
  • 在上一次,我们通过取余等...下面,我们使用链式存储结构来实现一个真正首尾相连的循环队列: class Node(object): '定义节点。' def __init__(self): '初始化:数据域与指针域。' self.data = None sel...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 20,228
精华内容 8,091
关键字:

循环队列是链式存储结构