0
点赞
收藏
分享

微信扫一扫

【数据结构】【期末】复习知识点总结

——算法、线性表——

概念明晰:随机存取、顺序存取、随机存储和顺序存储

下面完整的介绍一下这4个概念

1、存结构

分为随机存取非随机存取(又称顺序存取)

2、存结构

分为顺序存储随机存储

3、顺序存储结构

  

4、随机存储结构

--随机存储最典型的代表为链式存储:

  链式存储结构特点

  1、比顺序存储结构的存储密度小 (每个节点都由数据域和指针域组成,所以相同空间内假设全存满的话顺序比链式存储更多)。

  2、逻辑上相邻的节点物理上不必相邻。

  3、插入、删除灵活 (不必移动节点,只要改变节点中的指针)。

  4、查找结点时链式存储要比顺序存储慢。

  5、每个结点是由数据域和指针域组成

一、数据结构的概念

1、基本概念:

 

2、算法

(1)概念

(2)重要特性:

3、算法的评价标准(“好”的算法应该考虑达到以下目标)

4、算法的时空效率

(1)时间复杂度

根据算法写成的程序在执行时耗费时间的长度,记为T(n) = O(n)

(2)空间复杂度

根据算法写成的程序在执行时占用存储单元的长度记为S(n)

(3)语句频度

一个算法中的语句执行次数称为语句频度或时间频度,记为T(n)

(4)一般O(n)的计算方法:

(5)常见的时间复杂度有以下七种:

 

① O(1)常数型;② O(log2N)对数型;③ O(N)线性型;④ O(Nlog2N)二维型;⑤ O(N^2)平方型;⑥ O(N^3)立方型;⑦ O(2^N)指数型。

例如:

i=1;①
while (i<=n)
{
    i=i*2; ②
}
解:语句1的频度是1, 
设语句2的频度是f(n),则:2^f(n)<=n;f(n)<=log2n  
取最大值f(n)= log2n, T(n)=O(log2n )

二、线性表

1、顺序存储

(1)结构体的定义

typedef int Position;
typedef struct LNode * PtrToLNode;
struct LNode
{
    ElmenetType Data[ MAXSIZE ];
    Position Last;
};
typedef PtrToLNode List;

(2)顺序表的初始化

List MakeEmpty()
{
    List L; 
    L = (List)malloc(sizeof(struct LNode));
    L->Last = -1;   //Last 置为-1 表示表中没有数据元素
    Return L;
}
  • 通过L我们可以访问相应线性表的内容。比如:下标为i 的元素:L->Data[i]

  • 查询线性表的长度:L->Last+1;

(3)顺序表的查找(时间复杂度为O(n))

#define ERROR -1  /* 将错误信息 ERROR 的值定义为任一负数都可以 */
Position Find( List L, ElementType X )
{
    Position i = 0;
    While( i <= L->Last && L->Data[i] != X)
        i++;
    if( i > L->Last)
        return ERROR;   /* 如果没有找到,则返回错误信息 */
    else
        return i;   /* 找到后返回的是存储位置 */
}

(4)顺序表的插入 (时间复杂度为O(n))

bool Insert( List L, ElementType X, int i)
{ /* 在 L 的指定位序 i 前插入一个新元素 X; 位序 i 元素数组位置下标为 i-1 */
    Postion j;
    if(L->Last == MAXSIZE-1)
    {/* 表空间已满,不能插入 */
        printf("表满!\n");
        return false;
    }
    
    if( i<1 || i > L->Last+2)
    {/* 检查插入位序的合法性:是否在 1~n+1; n为当前元素个数,即Last+1 */
        printf("位序不合法!\n");
        return false;
    }
    
    for( j = L->Last; j >= i-1; j--) /*Last 指向序列最后元素an */
        L->Data[j+1] = L->Data[j]; /* 将位序为 i 及以后的元素顺序向后移动 */
    L->Data[i-1] = X;   /* Last 仍指向最后一个元素 */
    L->Last++;
    return true;
}

(5)顺序表的删除(时间复杂度为O(n))

bool Delete(List L, int i)
{  /*从 L 中删除指定位序 i 的元素,该元素数组下标为 i-1*/
    Position j;
    
    if(i < 1 || i > L->Last + 1)/* 检查空表及删除位序的合法性*/ 
    {
        printf("位序%d不存在元素",i);
        return false;
    } 
    
    for( j = i; i <= L->Last; j++)
        L->Data[j-1] = L->Data[j];/*将位序 i+1 及以后的元素顺序向前移动*/ 
    L->Last--;/*Last 仍指向最后元素*/ 
    return true; 
}

2、链表存储

(1)结构体的定义(时间复杂度为O(n))

typedef struct LNode * PtrToLNode;
struct LNode
{
    ElementType Data;
    PtrToLNode Next;
};
typedef PtrToLNode Position; /*这里的位置是结点的地址 */
typedef PreToLNode List;

(2)求表长(时间复杂度为O(n))

int Length(List L)
{//默认该链表是有头结点的   
    Position p;
    int i=0;  /* 初始化计数器 */
    
    //单向链表的遍历(三部曲)
    p = L->next; /* p指向表的第 1 个结点 */
    while(p)
    { /* 遍历单链表,统计结点数 */ 
        p=p->next;  
        i++; 
    } 
    return i;
}

(3)判空

int ListEmpty(LinkList L)
{   //若 L 为空,则返回1,否侧返回 0
    if(L->Next) //非空
        return 0;
    else
        return 1;
}

 

(4)查找(时间复杂度为O(n))

①按序号查找 FindKth(时间复杂度为O(n))

#define ERROR -1 /* 一般定义为表中元素不可能取到的值 */
​
ElementType FindKth(List L, int K) 
{   /* 根据指定的位序 K, 返回 L 中相应的元素 */
    Position P;
    int cnt = 1; /* 位序从 1 开始 */
    p = L; /* p 指向 L的第 1 个结点 */
    while(p && cnt < K)
    {
        p = p->next;
        cnt++
    } 
    
    if((cnt == K) && p)
        return p->Data; /* 返回第 K 个 */
    else
        return ERROR;   /* 否则返回错误信息 */
} 

②按值查找,即定位 Find(时间复杂度为O(n))

#define ERROR NULL /*空地址表示错误 */
​
Position Find( List L, ElementType X)
{
    Position p = L;/* p指向 L 的第 1 个元素 */
    while(p && p->Data != x)
    {
        p = p->Next;
    }
    
    if(p)
         return p;
    else
        return ERROR;
}

(5)链表的插入(时间复杂度为O(n))

 

 

int  ListInsert_L(LinkList &L, int i,ElementType e)
{
    p = L;
    j = 0;
    while(p&& j<i-1)
    {//寻找第 i-1 个结点
        p = p->next;
        ++=j;
    }
    if(!p || j > i-1)
        return ERROR;//
    s = (LinkList)malloc(sizeof(LNode));//生成新结点s
    s->data = e;        //将结点s 的数据域的值 更新为 e
    s->next = p->next;  //将结点s 插入 L 中
    p->next = s;
    return OK;
}

(6)创建链表(时间复杂度为O(n))

1、带头结点的【头插法】(时间复杂度为O(n))

 

/* 带头结点的插入创建 */
void createListHead( Linklist L, int n )
{ 
        //建立头结点
        L = (LNode*)malloc(sizeof(struct LNode));
        L->Next = NULL;
        //建立单链表(头插法)
        LNode *temp = NULL;
        //申请空间,写入数据
        for(int i = 0; i < n; i++)
        {
            tmp = (LNode*)malloc(sizeof(struct LNode)); /* 申请、填装结点 */
            scanf("%d",&tmp->Data);//输入元素值
            //插入到头结点的后面
            tmp->Next = L->Next; 
            L->Next = tmp;     
        }
}

2、带尾结点的插入【尾插法】(时间复杂度为O(n))

 

/*带尾结点的插入*/
void CreateList_L( Listlist &L, int n )
{ //正位序数输入 n 个元素的值,建立带表头结点的单链表L
        //建立头结点
        L = (LNode*)malloc(sizeof(struct LNode));
        L->Next = NULL;
        //建立单链表(尾插法)
        LNode r = L; //尾指针指向头结点
        //申请空间,写入数据
        for(int i = 0;i < n; i++)
        {
            LNode *tmp = (LNode*)malloc(sizeof(struct LNode)); /* 申请新结点 */
            scanf("%d",&tmp->Data); //输入元素
            tmp->Next = NULL;
            //插入到尾结点后面
            r->next = temp; 
            r = tmp;       //r指向新的尾结点
        }
}

(7)删除(时间复杂度为O(n))

 

 

 

//将线性表L 中第 i 个数据元素删除
int ListDelete_L(LinkList &L, int i, ElementType &e)
{
    p=L;
    int j=0;
    while(p->next && j < i-1)
    {//寻找第 i 个结点,并令p指向其前驱
        p = p>next;
        ++j;
    }
    if(!(p->next)||j < i-1)
        return ERROR;//删除位置不合理
    
    q = p->next;    //临时保存被删除结点的地址以备释放
    p->next = q->next; //改变被除结点的驱结点的指针域
    e = q->data;    //保存被删除结点的数据域
    free(q);        //释放被删除结点的空间
    return OK;
}

3、二者时间复杂度和优缺点的比较

1、两者复杂度比较

查找插入删除
顺序表O(1)O(1)O(n)通过下标直接找到待操作元素,主要时间花在移动元素上。
链表O(n)O(n)主要时间用于找到插入元素的位置O(n)主要时间用于找到待删除元素的位置

2、两者优缺点比较

数组优点缺点
随机访问性强;查找速度快插入和删除效率低;可能浪费内存;内存空间要求高,必须有足够的连续内存空间;数组大小固定,不能动态拓展
链表优点缺点
插入删除速度快;内存利用率高,不会浪费内存;大小没有固定,拓展很灵活。不能随机查找,必须从第一个开始遍历,查找效率低

三、栈

1、栈的顺序存储实现

(1)顺序栈结构体的定义

typedef int Position;
typedef int ElementType;
typedef struct SNode *PtrToNode;
struct SNode
{
    ElementType * Data;  /*存储元素的数组*/
    Position Top;        /*栈顶指针*/
    int MaxSize;         /*堆栈最大容量*/ 
};
typedef PtrToNode Stack;

(2)顺序栈的创建

Stack CreateStack(int MaxSize) /*顺序栈的创建*/ 
{
    Stack S = (Stack)malloc(sizeof(struct SNode));
    S->Data = (ElementType *)malloc(MaxSize * sizeof(ElementType));
    S->Top = -1;               /*"-1"表示空栈  "MaxSize-1"表示满栈*/ 
    S->MaxSize = MaxSize;      /*指定栈的最大容量*/ 
    return S;
}

(3)判满

bool IsFull(Stack S)        /*判断栈是否满了*/ 
{
    return(S->Top == S->MaxSize-1);
}

(4)判空

bool IsEmpty(Stack S)   /*判断堆栈是否为空*/ 
{
    return(S->Top == -1);
}

(5)入栈

bool Push(Stack S, ElementType X)   /*顺序栈的 入栈 操作*/ 
{ 
    if(IsFull(S)) 
    {
        printf("堆栈满!");
        return false;
    }
    else
    {
        S->Data[++(S->Top)] = X;    /*若是栈不满,则Top加 1,并将新元素放入Data数组的Top位置中*/ 
        return true;
    }
}

(6)出栈

ElementType Pop(Stack S) /*顺序栈 的 出栈 操作*/ 
{
    if(IsEmpty(S))      
    {
        printf("堆栈空!");
        return ERROR;                   /*ERROR 是 ElementType 类型的特殊值,标志错误。必须是正常栈元素数据不可能取到的值 */ 
    }
    else
        return(S->Data[(S->Top)--]);    /*若不空,返回Data[Top],同时将Top减 1*/  
}

2、栈的顺序存储实现

(1)顺序栈结构体的定义

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

(2)顺序栈的创建

Stack CreateStack()
{   /*构建一个堆栈的头结点,返回该结点指针*/ 
    Stack S;
    S = (Stack)malloc(sizeof(struct SNode));
    S->Next = NULL;
    return S;
}

(3)判空

bool IsEmpty(Stack S)
{   /*判断堆栈 S 是否为空,若是返回 true,否则返回 false*/ 
    return(S->Next == NULL);
}

(4)判满 注意:链栈,不必判断堆栈是否满

(5)入栈

bool Push(Stack S, ElementType X)
{   /*将元素 X 压入堆栈 S */ 
    PtrToSNode TmpCell;
    TmpCell = (PtrToSNode)malloc(sizeof(struct SNode));
    TmpCell->Data = X;
    
    //头插法
    TmpCell->Next = S->Next;
    S->Next =TmpCell;
    return true;
}

(6)出栈

ElementType Pop(Stack S) ElementType Pop(Stack S)
{   /*删除并返回堆栈 S 的栈顶元素*/  
    PtrToSNode FirstCell;
    ElementType TopElem;
    
    if(IsEmpty(S))
    {
        printf("堆栈空!");
        return ERROR;
    }
    else
    {
        FirstCell = S->Next;
        TopElem = FirstCell->Data;
        S->Next = FirstCell->Next;
        free(FirstCell);
        return TopElem;                         
    }
}/*顺序栈 的 出栈 操作*/ 
{
    if(IsEmpty(S))      
    {
        printf("堆栈空!");
        return ERROR;                   /*ERROR 是 ElementType 类型的特殊值,标志错误。必须是正常栈元素数据不可能取到的值 */ 
    }
    else
        return(S->Data[(S->Top)--]);    /*若不空,返回Data[Top],同时将Top减 1*/  
}

3、栈的应用

四、队列

1、队列的顺序存储实现

(1) 循环队列的结构体定义

typedef int Status; 
typedef int QElemType; /* QElemType类型根据实际情况而定,这里假设为int */
/* 循环队列的顺序存储结构 */
typedef struct QNode
{
    QElemType data[MAXSIZE];
    int front;      /* 头指针 */
    int rear;       /* 尾指针,若队列不空,指向队列尾元素的下一个位置 */
}SqQueue;

(2)生成空队列

/* 初始化一个空队列Q */
Status CreateQueue(SqQueue *Q)
{
    SqQueue *Q = (SqQueue)malloc(sizeof(struct QNode));
    Q->data = (ElementType*)malloc(MaxSize * sizeof(ElementType));
    Q->front = Q->rear = 0;
    return  OK;
}

(3)判空

bool IsEmpty(SqQueue *Q)
{
    return(Q->front == Q->rear);
}

(4)判满

bool IsFull(SqQueue *Q)
{
    return((Q->rear+1)% MaxSize == Q->front);
}

(5)入队

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

(6)出队

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

2、队列的链式存储实现

(1)队列的链式存储结构体定义

typedef int Status; 
typedef int QElemType; /* QElemType类型根据实际情况而定,这里假设为int */
typedef struct QNode    /* 结点结构 */
{
   QElemType data;
   struct QNode *next;
}QNode,*QueuePtr;
​
typedef struct          /* 队列的链表结构 */
{
   QueuePtr front,rear; /* 队头、队尾指针 */
}LinkQueue;

(2)生成空队列

/* 构造一个空队列Q */
Status InitQueue(LinkQueue *Q)
{ 
    Q->front=Q->rear=(QueuePtr)malloc(sizeof(QNode));
    if(!Q->front)
        exit(OVERFLOW);
    Q->front->next=NULL;
    return OK;
}

(3)判空

Status QueueEmpty(LinkQueue Q)
{ 
    if(Q.front==Q.rear)
        return TRUE;
    else
        return FALSE;
}

(4)判满 链式队列,不必判断堆栈是否满

(5)入队

/* 插入元素e为Q的新的队尾元素 */
Status EnQueue(LinkQueue *Q,QElemType e)
{ 
    QueuePtr s=(QueuePtr)malloc(sizeof(QNode));
    if(!s) /* 存储分配失败 */
        exit(OVERFLOW);
    s->data=e;
    s->next=NULL;
    Q->rear->next=s;    /* 把拥有元素e的新结点s赋值给原队尾结点的后继,见图中① */
    Q->rear=s;      /* 把当前的s设置为队尾结点,rear指向s,见图中② */
    return OK;
}

(6)出队

/* 若队列不空,删除Q的队头元素,用e返回其值,并返回OK,否则返回ERROR */
Status DeQueue(LinkQueue *Q,QElemType *e)
{
    QueuePtr p;
    if(Q->front==Q->rear)
        return ERROR;
    p=Q->front->next;       /* 将欲删除的队头结点暂存给p,见图中① */
    *e=p->data;             /* 将欲删除的队头结点的值赋值给e */
    Q->front->next=p->next;/* 将原队头结点的后继p->next赋值给头结点后继,见图中② */
    if(Q->rear==p)      /* 若队头就是队尾,则删除后将rear指向头结点,见图中③ */
        Q->rear=Q->front;
    free(p);
    return OK;
}

五、栈和队列操作的特点

相同点不同点
堆栈(FILO)只允许在端点处插入和删除元素;栈是先进后出或者后进先出;栈是只能在表的一端进行插入和删除操作的线性表
队列(FIFO)只允许在端点处插入和删除元素;队列是先进先出;队列是只能在表的一端进行插入,然后在另外一端进行删除操作的线性表

六、数组存储地址的计算

数组类型存储地址的计算(a是数组首地址,len是每个数组元素所占长度)
一维数组a[i]的存储地址:a+i*len
二维数组:a[m] [n]按行存储:a+(i * n+j) * len;按列存储:a+(j * m+i) * len
例子:数组存储地址的计算示例:
1)已知一维数组a中每个元素占用2个字节,求a[10]的存储地址?
    答:a[10]的存储地址为:a+10*2=a+20
2)已知二维数组a[4][5]中, 每个元素占用2个字节,求元素a[3][2]按行为主序存储的存储地址和按列为主序存储的存储地址?
答:     按行存储:a+(35+2) *2 = a+34
        按列存储:a+(24+3) *2 = a+22
举报

相关推荐

0 条评论