408数据结构考研笔记——第二章线性表

6 篇文章 1 订阅
6 篇文章 1 订阅

一 线性表

1.基础概念

2.基本操作

3.具体代码表示

1.顺序表

2.单链表 

3.双链表

4.循环链表

5.静态链表

二 课后习题(持续更新ing)


一 线性表

1.基础概念

    逻辑结构,具有相同数据类型的n个数据元素的有限序列逻辑特性——除首元素都有直接前驱,除尾元素都有直接后继。

特点:元素个数有限;元素逻辑顺序性(有先后次序);数据元素都是单个元素;元素数据类型相同,占用相同大小的存储空间;抽象性,仅讨论元素逻辑关系而不考虑元素代表的内容。

总结:讨论逻辑,类似仓库顺序排序,且形式上相同,不考虑内容表示。

存储结构

    1.顺序存储——顺序表,地址连续空间,特点是逻辑顺序与物理顺序相同,其中位序从1开始,数组下标从0开始

    2.链式存储——链表,包括单链表、双链表、循环单链表、循环双链表和静态链表(数组下标代替指针作用)

    3.顺序表和链表对比

顺序表链表
存取方式顺序存取或随机存取顺序存取
逻辑+物理结构逻辑物理都连续逻辑连续物理可不连续
查找

按值有序O(logn)无序O(n)

按位O(1)

按值O(n)

按位O(n)

插入和删除O(n)仅修改O(1),查找需O(n)
空间分配静态不好修改,动态麻烦随用随申请,更灵活
存储已知长度方便未知长度方便,但存储密度低
运算查找方便插入删除方便

 总结:顺序表适合进行数据查找,链表适合弹性扩充和增删操作

2.基本操作

InitList(&L):初始化,建立空表

Length(L):求表长,针对链表,顺序表结构体写明表长

LocateElem(L, e):按值查找

GetElem(L, i):按位查找

ListInsert(&L, i, e):插入元素(包括头插法和尾插法)

ListDelete(&L, i, &e):删除元素

PrintList(L):输出

Empty(L):判空,针对链表,顺序表可用L.length看

DestoryList(&L):销毁,针对链表,free(p)

3.具体代码表示

1.顺序表

//顺序表静态分配

#define MaxSize 10
typedef struct{
    int data[MaxSize]    //ElemType data[MaxSize];
    int length;
}SqList;

//空间为 MaxSize*sizeof(ElemType)

//顺序表动态分配

#define InitSize 10    //初始长度
typedef struct{
    SqList *data;
    int MaxSize;
    int length
}SqList, *ListPtr;

//分配语句——malloc
L.data = (ListPtr)malloc(sizeof(SqList) * InitSize);

顺序表初始化——InitList(&L):

//顺序表初始化————数组
void InitList(SqList &L)
{
    for(int i = 0; i < MaxSize; i++)
    {
        L.data[i] = 0;
    }
    L.length = 0;
}

int main()
{
    SqList L;
    InitList(L);
    ……
    return 0;
}

 顺序表输出——PrintList(L):

//顺序表输出
void PrintList(L)
{
    for(int i = 0; i < L.length; i++)
        printf("data[%d] = %d\n" , i, L.data[i]);
}

顺序表插入——ListInsert(&L, i, e) :

//顺序表插入
bool ListInert(SqList &L, int i, int e)
{
    if(i < 0|| i > L.length + 1)
        return false;
    if(L.length >= L.MaxSize)        //此时要求初始分配空间要大于数组长度,切切
        return false;
    for(int j = L.length; j >=i; j--)    
        L.data[j] = L.data[j-1];
    L.data[i-1] = e;
    L.length++;
    return true;
}

顺序表删除——ListDelete(&L, i, &e):

//顺序表元素删除————ListDelete(&L, i, &e), i是位序

bool ListDelete(SqList &L, int i, int &e)    //也可用返回值返回e值,需将函数类型换为int
{
    if(i < 1|| i > length)
        return false;
    e = L.data[i-1];
    for(int j = i; j < L.length-1; j++)
        L.data[j-1] = L.data[j];
    L.length--;
    return true;
}

顺序表查找:

//顺序表按位查找————GetElem(L, i)

int GetElem(SqList L, int i)
{
    return L.data[i-1];
}

//顺序表按值查找————LocateElem(L, e)
{
    for(int i = 0; i < length-1; i++)
        if(L.data[i]==e)
            return i+1;
    return -1;
}

2.单链表 

//单链表结构体
typedef struct LNode{
    ElemType data;
    struct LNode *next;
}LNode, *LinkList;

//节点声明
LNode *p = (LNode*)malloce(sizeof(LNode));

//初始化空链表
bool InitList(LinkList &L)
{
    L = NULL;
    return true;
}

//判空
bool Empty(LinkList L)
{
    if(L == NULL)
        return true;
    return false;
}

//求表长
int Length(LinkList L)
{
    int len = 0;
    LNode *p = L;
    while(p->next != NULL)
    {
        p = p->next;
        len++;
    }
    return len;
}

建立单链表:

//头插法建立单链表

LinkList List_HeadInsert(LinkList &L)
{
    LNode *s;
    int x;
    L = (LinkList)malloc(sizeof(LNode));
    L->next = NULL;
    scanf("%d", &x);
    while(x!=-1){
        s=(LinkList)malloc(sizeof(LNode));
        s->data = x;
        s->next = L->next;
        L->next = s;
        scant("%d", &x);
    }
    return L;
}

//强调单链表用LinkList
//强调节点用LNode


//尾插法建立单链表

LinkList List_TailInsert(LinkList &L)
{
    int x;
    L = (LinkList)malloc(sizeof(LNode));
    L->next = NULL;
    LNode *s = (LNode *)malloc(sizeof(LNode));
    LNode *p = L;
    scanf("%d", &x);
    while(x!=-1)
    {
        s->data = x;
        r->next = s;
        r = s;
        scanf("%d", &x);
    }
    r->next = NULL;
    return L:
}

 按位插入:

//在第i个位置插入元素e
//带头节点

bool LinkInsert(LinkList &L, int i, int e)
{
    if(i<1)
        return false;
    LNode *p;
    int j = 0;
    p = L;
    while(p!=NULL && j<i+1)
    {
        p = p->next;
        j++;
    }
    if(p == NULL)
    {
        return false;
    }
    LNode *temp = (LNode *)malloce(sizeof(LNode));
    temp->data = e;
    temp->next = p->next;
    p->next = temp;
    return true;
}

//不带头节点

bool LinkInsert(LinkList &L, int i, int e)
{
    if(i < 1)
        return false;
    if(i == 1)
    {
        LNode *temp = (LNode *)malloc(sizeof(LNode));
        temp ->data = e;
        temp ->next = L;
        L = temp;
        return true;
    }
    //其他和带头结点的一样
}

//单链表后插

bool InsertNextNode(LNode *p, int e)
{
    if(p == NULL)  return false;
    LNode *s = (LNode *)malloc(sizeof(LNode));
    s->data = e;
    s->next = p->next;
    p->next = s;
    return true;
}

//单链表前插————元素后移

bool InsertPriorNode(LNode *p, int e)
{
    if(p == NULL)  return false;
    LNode *s = (LNode *)malloc(sizeof(LNode));
    s->next = p->next;
    p->next = s;
    s->data = p->data;
    p->data = e;
    return true;
}

 按位删除:

//带头结点

bool ListDelete(LinkList L, int i, int &e)
{
    if(i < 1)  return false;
    LNode *p = L;
    int j=0;
    while(p != NULL && j<i-1)
    {
        p = p->next;
        j++;
    }
    if(p == NULL || p->next == NULL)  return false;
    LNode *temp = p->next;    //p是低i-1个节点,temp是要删除的节点
    e = temp->data;
    p->next = temp->next;
    free(temp);
    return true;
}

删除指定节点:

//删除指定节点p

bool ListDelete(LNode *p, int &e)
{
    if(p == NULL)  return false;
    LNode *temp = p->next;
    e = p->data;
    p->data = temp ->data;    //将后节点值前移后通过释放后节点达到链长-1,数据消失的效果
    p->next = temp ->next;
    free(temp);
    return true;
}

 按位查找:

//单链表按位查找
//带头结点

LNode * GetElem(LinkList L, int i)
{
    if(i<0) return NULL;
    LNode *p = L;
    int j=0;
    while(p != NULL && j<i)
    {
        p = p->next;
        j++;
    }
    return p;
}

//该函数可用在插入删除操作下所有寻找节点的操作中进行代码封装(避免代码重复,简洁、易维护)

按值查找:

//单链表按值查找
//带头结点

LNode * LocateElem(LinkList L, int e)
{
    LNode *p = L->next;
    while(p!=NULL && p->data!=e)
        p = p->next;
    return p;
}

 3.双链表

//双链表节点

typedef struct DNode{
    ElemType data;
    DNode *prior, *next;
}DNode, *DLinkList;

//双链表初始化

bool InitDLinkList(DLinkList &L)
{
    L = (DNode *)malloc(sizeof(DNode));
    if(L == NULL)  return false;
    L->prior = NULL;
    L->next = NULL;
    return true;
}

//判空

bool Empty(DLinkList L)
{
    return (L->next == NULL) ? true : false;
}

 双链表插入:

//双链表后插元素

bool InsertNextDNode(DNode *p, DNode *s)
{
    if(p == NULL || s == NULL)  return false;
    s->next = p->next;    //指针顺序不能乱,切切
    if(p->next != NULL)
        p->next->prior = s;
    s->prior = p;
    p->next = s;
    return true;
}

双链表删除:

//删除整个链表

void Destroy(DLinkList &L)
{
    while(L->next != NULL)
        DeleteNextDNode(L);
    free(L);
    L = NULL;
}

//删除p节点的后继节点

bool DeleteNextDNode(LNode *p)
{
    if(p == NULL)  return false;
    DNode *temp = p->next;
    if(temp == NULL)  return false;
    p->next = temp->next;
    if(temp->next != NULL)
        temp->next->prior = p;
    free(temp);
    return true;
}

 4.循环链表

//单循环链表节点

typedef struct LNode{
    ElemType data;
    LNode *next
}LNode, *LinkList;

//初始化单循环链表

bool InitList(LinkList &L)
{
    L = (LNode *)malloc(sizeof(LNode));
    if(L == NULL)  return false;
    L->next = L;
    return true;
}

//判空

bool Empty(LinkList L)
{
    if(L->next == L)  return true;
    return false;
}

其他操作不再展开,但核心思想就是理清链表指针的关系,注意后指针不可随意断开,可能会引起链表丢失,对于前操作(如前插或删除前看元素),有向前指针就用指针,没有指针就将后值前移再删除后节点

5.静态链表

#define MaxSize 10
typedef struct Node{
    Elemtype data;
    int next;        //利用数组下标做游标,代替指针功能
}SLinkList[MaxSize];

二 课后习题(持续更新ing)

1.从顺序表中删除具有最小值的元素(假设唯一)并由函数返回被删除的元素的值。空出的位置由最后一个元素填补,若顺序表为空,则显示出错信息并退出程序。

#define MaxSize 10
typedef struct{
    int data[MaxSize];
    int length;
}SqList

bool DeleteElem(SqList &L, int &e)
{
    if(L.length == 0)  return false;
    int maxELem = L.data[0];
    int maxIndex = 0;
    if(L.data[maxIndex+1] > L.data[maxIndex] && maxIndex<L.length)
    {
        maxElem = L.data[maxIndex+1];
        maxIndex++;
    }
    e = maxElem;
    L.data[maxIndex] = L.data[length - 1];
    length--;
    return true;
}

2.设计一个高效算法,将顺序表L所有元素逆置,要求算法的空间复杂度为O(1)。

#define MaxSize 10
typedef struct{
    int data[MaxSize];
    int length;
}SqList;

void TurnList(SqList &L)
{
    int times = length/2;
    for(int i=0; i<times; i++)
    {
        int temp = L.data[i];
        L.data[i] = L.data[L.length-i-1];
        L.data[L.length-i-1] = temp;
    }
}

3.对长度为n的顺序表L,编写一个时间复杂度为O(n)、空间复杂度为O(1)的算法,该算法删除线性表中所有值为x的数据元素。

#define MaxSize 100
typedef struct{
    int data[MaxSize];
    int length;
}SqList;

void DeleteElemX(SqList &L, int x)
{
    int num = 0;
    for(int i=0; i<n; i++)
    {
        if(L.data[i] == x)
        {
            num++;
        }
        data[i-num] == data[i];
    }
    L.length = n-num;
}

4.从有序顺序表中删除其值在给定值s与t之间(s<t)的所有元素,若s和t不合理或顺序表为空,则显示错误信息并退出运行。

#define MaxSize 10
typedef struct{
    int data[MaxSize];
    int length
}SqList;

bool DeleteElemStoT(SqList &L, int s, int t)    //通俗方法,但可能不会满分
{
    if( s>=t || L.length <1)  return false;
    int num = 0;
    for(int i=0; i<L.length-1; i++)
    {
        if(L.data[i] >= s && L.data[i]<= t)
            num++;
        else
            L.data[i-num] = L.data[i];
    }
    L.length = L.length - num;
    return true;
}


bool DeleteElemStoT(SqList &L, int s, int t)    //正确方法,注意题目中的“有序条件”
{ 
    int i,j;
    if(s>=t || L.length = 0)  return false;
    for(i=0; i<L.length&&L.data[i]<s;i++);    //单纯找符合小于s的第一个元素下标
    if(i>=L.length)  return false;
    for(j=i; j<L.length&&L.data[j]<=t;j++)    //找到大于t的第一个下标
    for(; j<L.length; i++,j++)
        L.data[i] == L.data[j];
    L.length = i;
    return true;

}

5.从顺序表中删除所有值在给定值s与t之间(包括s和t,s<t)的所有元素,若s或t不合理或顺序表为空,则显示出错信息并退出运行。

#define MaxSize 10
typedef struct{
    int data[MaxSize];
    int length;
}SqList;

bool DeleteRepeatElem(SqList &L, int s, int t)
{
    if(s>=t || L.length<1)  return false;
    int num = 0;
    for(int i=0; i<L.length-1; i++)
    {
        if(L.data[i]>=s && L.data[i]<=t)
            num++;
        else
            L.data[i-num] = L.data[i];
    }
    L.length = L.length - num;
    return true;
}

6.从有序顺序表中删除所有值重复的元素,使表中所有元素的值均不同。 

bool DeleteRepeatElem(SqList &L)
{
    if(L.length<1)  return false;
    int num = 0;
    for(int i=0; i<L.length-2; i++)
    {
        if(L.data[i] == L.data[i+1])
            num++;
        else
            L.data[i-num] = L.data[i];
    }
    L.length = L.length - num;
    return true;
}

7.将两个有序顺序表合并为一个新的有序顺序表,并由函数返回结果顺序表。

bool SumTwoList(SqList L1, SqList L2, SqList &L)
{
    
    if(L1.length + L2.length > L.MaxSize)  return false;
    int i=0,j=0;
    for(int k=0 ; k<L1.length+L2.length; k++)
    {
        if(L1.data[i]<=L2.data[j] || j = L2.length)    //王道书将此处拆分了,但意义相同
            L.data[k] = L1.data[i++];
        else if(L2.data[j] < L1.data[i] || i = L1.length)
            L.data[k] = L2.data[j++];        
    }
    L.length = k;    //循环中在k = L1.length + L2.length - 1 即最后一个元素时会进行加一操作
    return true;
}

8.已知在一维数组A[m+n] 中依次存放两个线性表(a1,a2...am)和(b1,b2,...,bn)。编写一个函数,将数组中两个顺序表的位置互换,即将(b1,b2,...,bn)放在(a1,a2,...,am)前面。

typedef int DataType;    //宏定义,方便根据需求更改

void Reverse(DataType A[], int left, int right, int size);

void ChangeListElem(DataType A[], int left, int right, int size)
{
    //将a的线性表下标全部+n,从A[0]~A[m-1]到A[n]~A[m+n-1],b全部-m
    //但因为未知两个线性表长度关系,并非单纯的一一对应置换问题,所以需另行考虑
    //我们先将整个数组颠倒,再将对应部分的线性表进行内部颠倒
 
    Reverse(A, 0, m+n-1, size);
    Reverse(A, 0, n-1, size);
    Reverse(A, n, m+n-1, size);
}

void Reverse(DataType A[], int left, int right, int size)
{
    if(left >= right || right >= size)  return false;
    int mid = (left + right)/2;
    for(int i=0; i<=mid-left; i++)
    {
        DataType temp = A[left+i];
        A[left+i] = A[right-i];
        A[right-i] = temp;
    }
}

9.线性表(a1,a2,...,an)中的元素递增有序且按顺序存储于计算机内。要求设计一个算法,完成用最少时间在表中查找数值为x的元素,若找到则将其与后继元素位置相互换,若找不到,则将其插入表中并是表中元素仍递增有序。

void FindElemX(SqList &S, int x)
{
    //对有序序列进行数据查找,首选二分查找

    if(S.length<1)  return false;
    if(x<S.data[0])                      //当x小于序列中所有元素时插入头部
    {
        for(int i=S.length - 1; i>0; i--) 
            S.data[i+1] = S.data[i];
        S.data[0] = x;
    }
    else if(x>S.data[lS.length - 1])     //当x大于序列中所有元素时插入尾部
        S.data[S.length] = x;
    else{
        int left = 0, right = S.length-1;
        int mid = (left + right)/2;
        while(left <= right)
        {
            if(S.data[mid] == x && mid != S.length -1)    //当最后一个元素是x时无需交换
            {
                int temp = S.data[mid];
                S.data[mid] = S.data[mid+1];
                S.data[mid+1] = S.data[mid];
                break;
            }
            else if(S.data[mid] > x)
            {
                right = mid - 1;
                mid = (left + right)/2;
            }
            else if(S.data[mid] < x)
            {
                left = mid + 1;
                mid = (left + right)/2;
            }
            if(left == right && S.data[mid] != x)
            {
                for(int i = length - 1; i>mid; i--)
                    S.data[i+1] = S.data[i];
                S.data[mid] = x;
                break;
            }
        }
    }

}

//和课本不太一样,但初步检查是没什么问题的

10.【2010统考真题】设将n(n>1)个整数存放到一维数组R中。设计一个在时间和空间两方面都尽可能高效的算法。将R中保存的序列循环左移p(0<p<n)个位置,即将R中数据由(X0,X1,...,Xn-1)变换为(Xp,Xp+1,...,Xn-1,X0,X1,...,Xp-1)。要求:

1)给出算法的基本设计思路

2)根据设计思路,采用C或C++或JAVA语言描述算法,关键之处给出注释

3)说明算法的时间和空间复杂度

设计思路:参考第8题,将数组从ab转ba,首先令数组整体倒置,再让(Xn-1,...,Xp+1,Xp)和(Xp-1,...,X0)两部分分别进行内部倒置。

详细算法

void Reverse(int R[], int left, int right, int ArraySize)
{
    if(left<0 || right>ArraySize-1)  return false;
    int temp;
    for(int i=0; i<(right-left+1)/2; i++)
    {
        temp = R[left+i];
        R[left+i] = R[right-i];
        R[right-1] = temp;
    } 
}

void Converse(int R[], int n, int p)
{
    Reverse(R, 0, n-1, n);
    Reverse(R, 0, p-1, p);
    Reverse(R, p, n-1, n-p);
}

//和书上好像不太一样,但感觉是对的

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值