Fork me on Github Fork me on Gitee

(三)线性表

上一篇:(二)时间复杂度和空间复杂度

1,顺序表

  1.1静态顺序表

  1.2动态顺序表

2.链表

  2.1单链表

  2.2循环链表

  2.3双向链表

3.总结

  3.1顺序表和链表的比较

顺序表

基本操作

InitList (&L) :构造一个空的线性表L。

DestroyList(&L) :销毁线性表L。

ClearList (&L):将L重置为空表。

ListEmpty(L) :若L为空表, 则返回true, 否则返回false。

ListLength(L) :返回L中数据元素个数。

GetElem(L,i,&e) :若,1≤i≤ListLength(L),则用e返回L中第i个数据元素的值。

LocateElem(L,e) :返回L中第1个 值与e相同的元素在 L中的位置 。若这样的数据元素不存在 , 则返回值为0。

PriorElem(r,,cur_e,&pre_e) :若cur_e是L的数据元素,且不是第一个,则用pre_e返回其前驱,否则操作失败,pre_e无定义。

NextElem(L,cur_e,&next_e) :若cur_e是L的数据元素,且不是最后一个,则用next_e返回其后继,否则操作失败,next_e无定义。

Listinsert(&L,i,e) :若1≤i≤ListLength(L)+1,在 L中第1个位置之前插入新的数据元素 e, L的长度加1。

ListDelete(&L,i) :若1≤i≤ListLength(L),删除L的第1个数据元素,L的长度减1。

TraverseList(L) :对线性表L进行遍历,在遍历过程中对 L的每个结点访问一次。

各操作时间复杂度:

按序查找:O(1):直接取用

按值查找:O(n)(最坏情况,具体看算法)

插入或删除:O(n):每次插入或删除元素都要对后面的所有元素后移或前移,若操作的元素在开头或末尾,则需要操作n次

静态顺序表

长度固定,一旦初始化后不再改变

//静态顺序表
#include<iostream>
using namespace std;
#define maxSize 5//定义最大长度
class StaticOrderList//==静态顺序表存储结构==
{
public:
    int * elem;//首地址
    int length;//已存入的元素的长度
    StaticOrderList();
    ~StaticOrderList();
};
StaticOrderList::StaticOrderList()
{
}
StaticOrderList::~StaticOrderList()
{
}

bool initList(StaticOrderList &L){//==初始化==
    L.elem=new int[maxSize];//静态分配空间
    if(!L.elem){
        cout<<"存储空间分配失败"<<endl;
        return false;
    }
    L.length=0;
    return true;
}

bool getElem(StaticOrderList &L,int i,int &e){//==取值==
    if(i<1||i>L.length){
        cout<<"取值范围无效"<<endl;
        return false;
    }
    e=L.elem[i-1];
    return true;
}

int locateElem(StaticOrderList &L,int e){//==查找==
    for (int i = 0; i < L.length; i++)
    {
        if(L.elem[i]==e){
            return i+1;
        }
    }
    cout<<"未查到该元素"<<endl;
    return 0;
}

bool listInsert(StaticOrderList &L,int i,int e){//==插入==
    if(i<1||i>L.length+1){
        cout<<"插入范围无效"<<endl;
        return false;
    }
    if(L.length==maxSize){
        cout<<"空间已满"<<endl;
        return false;
    }
    for(int j=L.length-1;j>=i-1;j--){
        L.elem[j+1]=L.elem[j];
    }
    L.elem[i-1]=e;
    L.length++;
    return true;
}

bool listDelete(StaticOrderList &L,int i){//==删除==
    if(i<1||i>L.length){
        cout<<"删除范围无效"<<endl;
        return false;
    }
    for(int j=i;j<=L.length-1;j++){
        L.elem[j-1]=L.elem[j];
    }
    L.length--;
    return true;
}

void printList(StaticOrderList &L){
    cout<<"输出表:";
    for(int i=0;i<=L.length-1;i++){
        cout<<L.elem[i]<<" ";
    }
    cout<<endl;
}
int main(){
    StaticOrderList L;
    initList(L);
    listInsert(L,1,2);
    listInsert(L,1,4);
    listInsert(L,1,6);
    listInsert(L,1,3);
    listInsert(L,2,7);
    printList(L);
    listDelete(L,2);
    printList(L);
    cout<<L.length;
}

动态顺序表

长度不定,可根据需求申请新的空间

//动态顺序表
#include<iostream>
using namespace std;
#define initSize 3
class DynamicOrderList
{
public:
    int *elem;
    int length;
    int maxSize;
    DynamicOrderList(){};
    ~DynamicOrderList(){};
};

bool initList(DynamicOrderList &L){//==初始化==
    L.elem=(int *)malloc(initSize * sizeof(int));
    if(!L.elem){
        cout<<"存储空间分配失败"<<endl;
        return false;
    }
    L.length=0;
    L.maxSize=initSize;
    return true;
}

void increaseSize(DynamicOrderList &L,int len){//动态增加长度
    int *p=L.elem;
    L.elem=new int[L.maxSize + len];
    for(int i=0;i<L.length;i++){
        L.elem[i]=p[i];
    }
    L.maxSize+=len;
    delete p;
}

bool getElem(DynamicOrderList &L,int i,int &e){//==取值==
    if(i<1||i>L.length){
        cout<<"取值范围无效"<<endl;
        return false;
    }
    e=L.elem[i-1];
    return true;
}

int locateElem(DynamicOrderList &L,int e){//==查找==
    for (int i = 0; i < L.length; i++)
    {
        if(L.elem[i]==e){
            return i+1;
        }
    }
    cout<<"未查到该元素"<<endl;
    return 0;
}

bool listInsert(DynamicOrderList &L,int i,int e){//==插入==
    if(i<1||i>L.length+1){
        cout<<"插入范围无效"<<endl;
        return false;
    }
    if(L.length==L.maxSize){
        increaseSize(L,2);//空间满了之后再申请
    }
    for(int j=L.length-1;j>=i-1;j--){
        L.elem[j+1]=L.elem[j];
    }
    L.elem[i-1]=e;
    L.length++;
    return true;
}

bool listDelete(DynamicOrderList &L,int i){//==删除==
    if(i<1||i>L.length){
        cout<<"删除范围无效"<<endl;
        return false;
    }
    for(int j=i;j<=L.length-1;j++){
        L.elem[j-1]=L.elem[j];
    }
    L.length--;
    return true;
}

void printList(DynamicOrderList &L){
    cout<<"输出表:";
    for(int i=0;i<=L.length-1;i++){
        cout<<L.elem[i]<<" ";
    }
    cout<<endl;
}
int main(){
    DynamicOrderList L;
    initList(L);
    listInsert(L,1,2);
    listInsert(L,1,3);
    listInsert(L,2,7);
    printList(L);
    cout<<L.maxSize;
    listInsert(L,2,5);
    printList(L);
    cout<<L.maxSize;
}

链表

在物理存储单元上非连续,非顺序的存储结构

根据链表结点所含指针个数、指针指向和指针连接方式,可将链表分为单链表、循环链表、 双向链表、二叉链表、十字链表、邻接表、邻接多重表等。

其中单链表、循环链表和双向链表用 千实现线性表的链式存储结构,其他形式多用于实现树和图等非线性结构。

单链表

分类

普通单链表

带头节点的单链表

几个概念:

首元节点:链表中第一个数据的节点,普通单链表的第一个,带头结点的单链表的第二个

头节点:首元节点前设置的节点,其指针域指向首元节点,数据域可以不存储任何信息,也可以存储单链表的附加信息

头指针:链表的首地址,若无头节点,指向第一个数据,若有,指向第一个元素

增加头节点

1.增加头节点,对链表的第一个元素的操作与其他元素相同,无需特殊处理

2.无头节点的链表判空条件:L==NULL;有头节点的链表判空条件:L->next==NULL

带头节点的单链表的实现
//有头节点的单链表
/*ps:开始用的对象和指针,后来发现函数中实例化的对象是局部变量,返回出来的L
只能记录相邻的数据,之后的数据随函数释放而消失,所以改用malloc分配空间,对
地址的操作是可以记录的*/

#include<iostream>
using namespace std;
class SingleLinkedList{//==单链表的存储结构==
public:
    int data;
    SingleLinkedList *nextp;
    SingleLinkedList(){};
    ~SingleLinkedList(){};
};
typedef SingleLinkedList * Linkpoint;

void initList(Linkpoint &L){//==初始化==
    L=(Linkpoint)malloc(sizeof(SingleLinkedList));
    L->data=0;//头节点的data域存单链表的长度
    L->nextp=NULL;//nextp域存下一个节点地址
}

void listInsert(Linkpoint &L,int i,int e){//插入
    Linkpoint p=L;//在函数内对L操作都用新的指针,不要直接对L操作
    Linkpoint s=(Linkpoint)malloc(sizeof(SingleLinkedList));
    int j=1;
    while(p&&(j<i)){
        p=p->nextp;
        j++;
    }
    if(!p||j>i){
        cout<<"插入范围无效"<<endl;
        return;
    }
    s->data=e;
    s->nextp=p->nextp;
    p->nextp=s;
    L->data++;
}

void printList(Linkpoint &L){//==输出==
    Linkpoint p=L->nextp;
    cout<<"输出单链表"<<endl;
    while(p){//因为头节点最初指向NULL,每次插入后,尾节点都指向NULL
        cout<<p->data<<"->";
        p=p->nextp;
    }
    cout<<"NULL"<<endl;
    cout<<"单链表的长度为:"<<L->data<<endl;
}

void getElem(Linkpoint &L,int i){//==取值==
    Linkpoint p=L->nextp;
    int j=1;
    while (p&&(j<i)){
        p=p->nextp;
        j++;
    }
    if(!p||j>i){
        cout<<"取值范围无效"<<endl;
        return;
    }
    cout<<"第"<<i<<"个元素为:"<<p->data<<endl;
}

void locateElem(Linkpoint &L,int e){//==查找==
    Linkpoint p=L->nextp;
    int j=1;
    while(p){
        if(p->data==e){
            cout<<"元素"<<e<<"在第"<<j<<"位"<<endl;
            return;
        }
        p=p->nextp;
        j++;
    }
    cout<<"待查找元素不存在"<<endl;
}

void deleteElem(Linkpoint &L,int i){//==删除==
    Linkpoint p=L;
    Linkpoint s=(Linkpoint)malloc(sizeof(SingleLinkedList));
    int j=1;
    while(p&&(j<i)){
        p=p->nextp;
        j++;
    }
    if(!p||j>i){
        cout<<"删除范围无效"<<endl;
        return;
    } 
    Linkpoint q;
    q=p->nextp;
    p->nextp=q->nextp;//令p-next指向p-next-next就把p-next删除了
    delete q;
    L->data--;
}

//建立单链表-头插法和尾插法
void createList_H(Linkpoint &L,int n){//==输入n个字符插入单链表==
    L=(Linkpoint)malloc(sizeof(SingleLinkedList));
    L->nextp=NULL;
    L->data=0;//L.data存长度
    cout<<"输入数据按回车继续:";
    for(int i=0;i<n;i++){
        Linkpoint p=(Linkpoint)malloc(sizeof(SingleLinkedList));
        cin>>p->data;
        p->nextp=L->nextp;
        L->nextp=p;
        L->data++;
    }
}

void createList_R(Linkpoint &L,int n){//==尾插法==
    L=(Linkpoint)malloc(sizeof(SingleLinkedList));
    L->nextp=NULL;
    L->data=0;
    Linkpoint r=L;
    for(int i=0;i<n;i++){
        Linkpoint p=(Linkpoint)malloc(sizeof(SingleLinkedList));
        cin>>p->data;
        p->nextp=NULL;
        r->nextp=p;
        r=p;
        L->data++;
    }
}
int main(){
    Linkpoint L;
    createList_R(L,5);
    printList(L);
}

循环链表

循环链表就是一种特殊的单链表,其尾节点的指针指向了头节点,使节点首尾相连

循环链表和单链表的主要区别就是遍历链表时,判断当前指针是否指向尾节点的条件不同

单链表:p=NULL或p->next=NULL

循环链表:p=L或p->next=NULL

双向链表

循环单链表中查找后继节点的执行时间为O(1),而查找其前驱节点的时间为O(n),因为只能顺指针方向像狗查询,双向链表加上前驱指针后解决了这个问题

双向链表和单链表的区别就是双链表有前后两个指针,分别指向其前驱和后继

对于双链表,有d->next->prior=d->prior->next=d

实现

因为初始化的节点也可以存数据,所以加入flag标志是否初始节点

此双链表的结构是可以随意插入删除,L始终是首节点的地址,输出是从L开始向后输出的

且首节点的len记录链表长度,当L地址发生变化时,确保新L的len还能记录长度

L地址变化的情况:首节点前插入,删除首节点

//双链表
//本程序的初始节点不是头节点,只是初始化后的第一个点
//开始用p.next==NULL&&p.prior==NULL来判断初始节点,然后对其data域操作,
//但是操作后,L的next和prior还是null的,所以增加flag来标志初始节点
#include<iostream>
using namespace std;

class DoubleLinkedList{//==双链表存储结构==
public:
    int data;
    int flag=0;//flag标志节点是否初始节点,默认不是初始节点
    int len;
    DoubleLinkedList * prior;
    DoubleLinkedList * next;
    DoubleLinkedList(){};
    ~DoubleLinkedList(){};
};

typedef DoubleLinkedList * DuLinkList;

void initList(DuLinkList &L){//==初始化==
    L=(DuLinkList)malloc(sizeof(DoubleLinkedList));
    L->prior=NULL;
    L->next=NULL;
    L->flag=1;//初始化
    L->len=0;//len记录链表长度
}

void listInsert_H(DuLinkList &L,int i,int e){//==前插==
    if(L->flag){//如果是初始节点,把第一个数据插在其data域
        L->data=e;
        L->flag=0;//之后标志位置零
        L->len++;//长度+1
        return;
    }
    int j=1;
    int len=L->len;//记录旧长度
    DuLinkList p=L;
    while(p&&(j<i)){//p移动到待插位置
        p=p->next;
        j++;
    }
    if(!p||j>i){
        cout<<"插入范围无效"<<endl;
        return;
    }
    DuLinkList s=(DuLinkList)malloc(sizeof(DoubleLinkedList));
    s->data=e;
    s->prior=p->prior;
    if(p->prior){
        p->prior->next=s;//p的前驱的后继本来是p,现在是前插的s;(这里,
    }                    //若p是初始节点,则其没有前驱和后继节点)
    s->next=p;           //严蔚敏的教材上没有这一步的判断,导致NULL无
    p->prior=s;          //指针程序错误
    if(i==1){//如果,插在第一个元素前面,则更新L,使L始终为开头
        L=s;
    }
    L->len=len+1;//记录新长度
}

void listInsert_R(DuLinkList &L,int i,int e){//==后插==
    if(L->flag){
        L->data=e;
        L->flag=0;
        L->len++;
        return;
    }
    int j=1;
    int len=L->len;//记录旧长度
    DuLinkList p=L;
    while(p&&(j<i)){
        p=p->next;
        j++;
    }
    if(!p||j>i){
        cout<<"插入范围无效"<<endl;
        return;
    }
    DuLinkList s=(DuLinkList)malloc(sizeof(DoubleLinkedList));
    s->data=e;
    s->next=p->next;
    if(p->next){//同前插,若后继为NULL,则跳过,否则更新
        p->next->prior=s;
    }
    s->prior=p;
    p->next=s;
    L->len=len+1;
}

void printList(DuLinkList &L){//==输出==
    if(L->flag){
        cout<<"NULL<->NULL"<<endl;
        cout<<"链表长度为:0"<<endl;
        return;
    }
    DuLinkList p=L;
    cout<<"输出双链表"<<endl;
    cout<<"NULL<->";
    while(p){//因为头节点最初指向NULL,每次插入后,尾节点都指向NULL
        cout<<p->data<<"<->";
        p=p->next;
    }
    cout<<"NULL"<<endl;
    cout<<"链表长度为:"<<L->len<<endl;
}

void deleteElem(DuLinkList &L,int i){//==删除==
    int j=1;
    int len=L->len;//记录旧长度
    DuLinkList p=L;
    while(p&&(j<i)){
        p=p->next;
        j++;
    }
    if(!p||j>i){
        cout<<"删除范围无效"<<endl;
        return;
    }
    if(!(p->prior||p->next)){//分情况:初始节点
        L->flag=1;//初始节点,flag置1
    }else if(!p->prior){//首节点,此时需要更新L
        p->next->prior=NULL;
        L=p->next;
    }else if(!p->next){//尾节点
        p->prior->next=NULL;
    }else{//中间节点
        p->prior->next=p->next;
        p->next->prior=p->prior;
    }
    L->len=len-1;
}
int main(){
    DuLinkList L;
    initList(L);
    listInsert_H(L,1,23);
    listInsert_H(L,1,45);
    listInsert_H(L,2,12);
    deleteElem(L,2);
    deleteElem(L,1);
    deleteElem(L,1);
    printList(L);
}

顺序表和链表的比较

下一篇:(四)栈和队列

posted @ 2022-01-19 22:22  Tenerome  阅读(97)  评论(0编辑  收藏  举报