数据结构与算法---线性表

2020-11-16  本文已影响0人  烟火_jason

前言

这篇文章会介绍线性表的内容,其中线性表是1对1的逻辑结构,分别有 顺序表单链表单向循环链表双向链表双向循环链表,接下来介绍的代码都是用c语言的。

1. 顺序表

对于非空的线性表和线性结构,其特点如下:

接下来就是介绍线性表的顺序存储,简单来说就是一个数组开辟一段连续的空间,其中顺序存储的逻辑相邻,物理存储地址相邻。

1.1 准备和辅助的代码

#define MAXSIZE 100//数据的大小
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
typedef int ElemType;//数据类型的别名
typedef int Status;//返回的状态

//顺序表的结构体
typedef struct{
    ElemType *data;//数据
    int length;//长度
} Sqlite;

1.2顺序表的操作

因为顺序表其实就是一个数组,在初始化的时候就开辟好一定的空间,在这个过程中空间的大小是一直存在的。

//初始化顺序表
Status initList(Sqlite *L){
    L->data = malloc(sizeof(ElemType) * MAXSIZE);
    if(!L->data){
        return ERROR;
    }
    L->length = 0;
    return OK;
}

接下来就是对顺序表的增删改查等操作了。

//为顺序表插入数据
Status insertList(Sqlite *L,int index,ElemType e){
    if(index < 0 || (index > L->length && L->length > 0) || L->length >= MAXSIZE){
        return ERROR;
    }
    if(index < L->length){
        for (int j = L->length - 1; j >= index; j -- ) {
            L->data[j+1] = L->data[j];
        }
    }
    L->data[index] = e;
    L->length ++;
    return OK;
}

//删除某个下标的值
Status deleteListForIndex(Sqlite *L,int index){
    if(L->length == 0 || index >= L->length){
        return ERROR;
    }
    
    for (int j = index; j < L->length; j++) {
        L->data[j] = L->data[j+1];
    }
    L->length --;
    return OK;
}

//清空顺序表
Status clearList(Sqlite *L){
    L->length = 0;
    return OK;
}

//获取顺序表的长度
int getListLength(Sqlite L){
    return L.length;
}

//获取某个下标的值
Status getListElem(Sqlite L,int i,ElemType *e){
    if(L.length <= 0 || i >= L.length){
        return ERROR;
    }
    *e = L.data[i];
    
    return OK;
}

//遍历顺序表
Status traverList(Sqlite L){
    if(!L.data || L.length == 0){
        return ERROR;
    }
    for (int i = 0; i < L.length; i++) {
        printf("%d\t",L.data[i]);
    }
    printf("\n");
    return OK;
}

顺序表就暂时介绍到这里。

2.单链表

单链表是一种链式存取的数据结构,用一组地址任意的存储单元存放线性表中的数据元素。链表中的数据是以结点来表示的,每个结点的构成:元素数据元素的映象 +指针(指示后继元素存储位置),元素数据域就是存储数据的存储单元,指针就是连接每个结点的地址数据。

image.png
线性表的链式存储的最大的特点就是:数据与数据之间是不连续的,通过指针域来连接的。单链表的第一个结点叫首元结点,最后一个结点的指针域为空。在单链表中为了方便对其进行增加和删除的操作更加方便,一般会设置一个头结点。而这个头结点可以方便对首元结点的操作以及对空表和非空表的统一处理。并且这个头结点是在初始化的时候就创建了。

2.1单链表的操作

首先需要先定义好,单链表的数据结构。这个第一部分的顺序表的定义是有点区别的,其中ElemType data是数据域,struct Node *next是指针域。

typedef struct Node{
    ElemType data;
    struct Node *next;
} Node;

typedef Node *LinkList;

接下来就是初始化单链表,其中在初始化的时候就将头结点初始化出来了

//初始化
Status initLinkList(LinkList *L){
    //这个是初始化头结点
    *L = (LinkList)malloc(sizeof(Node));
    if(*L == NULL){
        return ERROR;
    }
    (*L)->next = NULL;
    return OK;
}

对单链表的数据插入:就是在链表的某个位置插入一条数据。首先要先找到需要插入的结点p,新插入的数据结点为s,先将s的next设置为结点p的下一个结点(p->next),然后再将结点p的next结点指向s。这样就完成了插入。

//插入
Status insertLinkList(LinkList *L,int index,ElemType e){
    
    int j = 1;//这个是计数用的,有头结点,就从1开始
    LinkList p,s;
    p = *L;
    while (p && j < index) {
        p = p -> next;
        j ++;
    }
    
    if(!p || j > index){
        return ERROR;
    }
    //定义需要插入的结点
    s = (LinkList)malloc(sizeof(Node));
    s->data = e;
    s->next = p->next;
    p->next = s;
    return OK;
}

单链表的遍历是最简单的,但是在遍历的时候需要注意的是,我们这个单链接是设置了一个头结点的,在遍历的时候不需要将头结点遍历出来的

//遍历单链表
Status ListTraverse(LinkList L){
    LinkList p = L->next;
    while (p) {
        printf("%d\t",p->data);
        p = p -> next;
    }
    printf("\n");
    return OK;
}

单链表的删除:就是删除指定位置的结点。也是首先找到需要删除的结点的前驱结点q,然后将需要删除的结点用一个临时指针变量s存放,将s的next指针指向q的next,然后再释放结点s。

//删除的结点,并将值返回
Status deleteLinkListItem(LinkList *L,int index,ElemType *e){
    LinkList s,q;
    int j = 1;
    q = (*L)->next;
    while (q && j < (index - 1)) {
        q = q->next;
        j ++;
    }
    
    if(!q || j > (index - 1)){
        return ERROR;
    }
    
    s = q->next;//需要删除的结点
    q->next = s->next;
    *e = s->data;
    free(s);
    return OK;
}

2.2单链表的头插法和尾插法的建立

头插法:将链表的左边称为链头,右边称为链尾。头插法建单链表是将链表右端看成固定的,链表不断向左延伸而得到的。头插法最先得到的是尾结点。

//头插法(创建一个有头结点的单链表)
void createLinkListHead(LinkList *L,int length){
    LinkList p;
    *L = (LinkList)malloc(sizeof(Node));
    (*L)->next = NULL;
    for (int i = 0; i < length; i ++) {
        p = (LinkList)malloc(sizeof(Node));
        p->data = i;
        p->next = (*L)->next;
        (*L)->next = p;
    }
}

尾插法:若将链表的左端固定,链表不断向右延伸,这种建立链表的方法称为尾插法。尾插法建立链表时,头指针固定不动,故必须设立一个搜索指针,向链表右边延伸,尾插法最先得到的是头结点。

//后插法 (创建一个有头结点的单链表)
void createLinkListTail(LinkList *L,int length){
    LinkList p,r;
    *L = (LinkList)malloc(sizeof(Node));
    r = *L;
    for (int i = 0; i < length; i ++) {
        p = (LinkList)malloc(sizeof(Node));
        p->data = i;
        r->next = p;
        r = p;
    }
    r->next = NULL;
}

3.单向循环链表

单向循环链表单链表的区别就是:

3.1单向循环链表的操作

定义的准备工作还是和单链表的一样。创建单向循环链表并且初始化数据,在创建的过程中有两种情况

//创建单向循环链表
void createLinkList(LinkList *L,int length){
    LinkList temp,target;
    for (int i = 0; i < length; i ++) {
        if(*L == NULL){
            *L = (LinkList)malloc(sizeof(Node));
            if(*L == NULL){
                return;
            }
            (*L)->data = i;
            (*L)->next = *L;
            
        }else{
            //要遍历查找到尾结点,然后在尾结点插入数据
            target = *L;
            while (target->next != *L) {
                target = target->next;
            }
            temp = (LinkList)malloc(sizeof(Node));
            if(temp == NULL){
                return;
            }
            temp->data = i;
            temp->next = *L;
            target->next = temp;
        }
    }
}

还有一种方式就是用一个指针变量始终保存着尾结点,创建的时候就只用尾指针来操作,从而省了每次的遍历查找

void createLinkList2(LinkList *L,int length){
    LinkList temp,r;
    for (int i = 0; i < length; i ++) {
        if(*L == NULL){
            *L = (LinkList)malloc(sizeof(Node));
            if(*L == NULL){
                return;
            }
            (*L)->data = i;
            (*L)->next = *L;
            r = *L;
        }else{
            temp = (LinkList)malloc(sizeof(Node));
            if(temp == NULL){
                return;
            }
            temp->data = i;
            temp->next = *L;
            r->next = temp;
            r = temp;
        }
    }
}

单向循环链表的遍历,也是以首元结点来作为标志来判断的

//遍历单向循环链表
void traveceLinkList(LinkList L){
    LinkList temp = L;
    do {
        printf("%d\t",temp->data);
        temp = temp->next;
    } while (temp != L);
    printf("\n");
}

单向循环链表插入数据,其中插入数据有两种情况

void insertLinkList(LinkList *L,int place,int num){
    if(*L == NULL){
        return;
    }
    LinkList temp,target;
    if(place == 0){
        //找到尾结点
        target = *L;
        while (target->next != *L) {
            target = target->next;
        }
        
        temp = (LinkList)malloc(sizeof(Node));
        if(temp == NULL){
            return;
        }
        temp->data = num;
        temp->next = *L;
        target->next = temp;
        *L = temp;
    }else{
        target = *L;
        int j = 1;//用来计数的
        while (target->next != *L && j != place ) {
            target = target->next;
            j ++;
        }
        
        if(place > j){
            return;
        }
        
        temp = (LinkList)malloc(sizeof(Node));
        if(temp == NULL){
            return;
        }
        temp->data = num;
        temp->next = target->next;
        target->next = temp;
        
    }
}

单向循环链表的删除,与插入是差不多的逻辑,也是分两种情况的

void deleteLinkList(LinkList *L,int place,ElemType *e){
    if(*L == NULL){
        return;
    }
    LinkList target,deleteItem;
    target = *L;
    //删除首元结点
    if(place == 0){
        if(target->next == *L){//本身
            deleteItem = *L;
            *e = deleteItem->data;
            *L = NULL;
        }else{
            //找到尾结点
            while (target->next != *L) {
                target = target->next;
            }
            deleteItem = *L;
            target->next = deleteItem->next;
            *L = deleteItem->next;
            *e = deleteItem->data;
            free(deleteItem);
        }
    }else{
        int j = 1;
        while (target->next != *L && j != place) {
            target = target->next;
            j ++;
        }
        if(j < place){
            return;
        }
        deleteItem = target->next;
        target->next = deleteItem->next;
        *e = deleteItem->data;
        free(deleteItem);
        
    }
    
}

4 双向链表

双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点后继结点,在双向链表尾结点的后继结点是为NULL的。在这部分内容中会设置一个头结点,其中这个头结点的前驱结点为NULL,数据域为-1,这头结点只是辅助功能。双链表的定义与上面的定义有所区别,多了一个前驱结点

typedef struct Node{
    ElemType data;
    struct Node *next;
    struct Node *prior;
} Node;

typedef Node *LinkList;

初始化双向链表,其中头结点在初始化的时候,前驱结点和后继结点都设置为NULL,数据域为-1,然后用后插法的方式给双链表赋值数据。

//创建双向链表
void createLinkList(LinkList *L){
    if(*L != NULL){
        return;
    }
    printf("创建双向链表\n");
    //初始化就有头结点
    *L = (LinkList)malloc(sizeof(Node));
    (*L)->prior = NULL;
    (*L)->next = NULL;
    (*L)->data = -1;
    
    LinkList temp;
    LinkList target = *L;
    
    for (int i = 0; i < 10; i ++) {
        temp = (LinkList)malloc(sizeof(Node));
        temp->data = i;
        temp->next = NULL;
        temp->prior = target;
        target->next = temp;
        target = temp;
    }
}

为了检验创建的双链表是否正确,打印遍历双链表

//打印双向链表
void showLinkList(LinkList L){
    if(L == NULL){
        return;
    }
    //头结点不打印出来的
    LinkList temp = L->next;
    while (temp != NULL) {
        printf("%d\t",temp->data);
        temp = temp->next;
    }
    printf("\n");
}

接下来就是双向链表的插入数据,传入插入的位置和数据,因为一开始就设置了有头结点,所以对首元结点的插入数据和其他位置的插入是没区别的,对需要找到插入数据的结点的前一个结点定义为target,然后对创建的结点定义为temp。首先需要对temp的next结点(需要插入数据的结点)的前驱结点改为temp,然后temp的next为target的next(需要插入数据的结点),然后target的next改为temp,temp的前驱结点(prior)为target结点,也要注意一下插入到最后一个结点。代码如下:

void insertLinkList(LinkList *L,int place,ElemType e){
    if(*L == NULL || place < 1){
        return;
    }
    LinkList target = (*L);
    
    LinkList temp = (LinkList)malloc(sizeof(Node));
    temp->data = e;
    temp->prior = NULL;
    temp->next = NULL;
    
    //找到需要插入的位置的前一个结点
    int index = 1;
    while (target != NULL && index != place) {
        target = target->next;
        index ++;
    }
    if(target == NULL){
        return;
    }
    //这是在最后的结点插入数据
    if(target->next == NULL){
        target->next = temp;
        temp->prior = target;
    }else{
        target->next->prior = temp;
        temp->next = target->next;
        target->next = temp;
        temp->prior = target;
    }
}

双向链表的指定位置的删除,找到需要删除结点的上一个结点target,需要删除的结点为deleteItem,将target的next指向deleteItem的next,然后deleteItem的next的前驱结点指向target。


image.png
void deleteLinkList(LinkList *L,int place){
    if(*L == NULL || place < 1){
        return;
    }
    //找到需要删除的结点的前一个结点
    LinkList target = *L;
    int index = 1;
    while (target != NULL && index < place) {
        target = target->next;
        index ++;
    }
    
    if(target->next == NULL || index > place){
        return;
    }
    
    LinkList deleteItem = target->next;
    target->next = deleteItem->next;
    if(deleteItem->next != NULL){
        deleteItem->next->prior = target;
    }
    free(deleteItem);
    
}

5.双向循环链表

双向循环链表的数据结构与双链表是一样的,区别就是:它的最后一个结点指向头结点,形成一个环。因此,从循环链表中的任何一个结点出发都能找到任何其他结点。

image.png

接下来就是创建一个带头结点的空的双向循环链表

//创建一个带头结点的空的双向循环链表
void initLinkList(LinkList *L){
    *L = (LinkList)malloc(sizeof(Node));
    (*L)->prior = (*L);
    (*L)->next = (*L);
    (*L)->data = -1;
}

对双向循环链表插入数据,要先找到插入数据结点的前驱结点,然后先给插入的结点temp分别设置前驱和后继结点这时还没打破链表的,再对前驱结点target的next指向temp,还要对如果temp的next不是尾结点的(没有指向头结点)前驱结点指向temp。


image.png
//双向循环链表插入数据
void insertLinkList(LinkList *L,int place,ElemType e){
    if(*L == NULL){
        return;
    }

    int index = 0;
    LinkList target = *L;
    // 查找需要插入的位置
    while (index < place) {
        target = target->next;
        index ++;
    }
    if(index > place){
        return;
    }

    LinkList temp = (LinkList)malloc(sizeof(Node));
    if(temp == NULL){
        return;
    }
    temp->data = e;
    temp->prior = target;
    temp->next = target->next;
    target->next = temp;
    if(temp->next != *L){
        temp->next->prior = temp;
    }

}

双向循环链表的删除,通过位置找到需要删除的结点,因为有了头结点所以首元结点的删除与其他结点的删除是一样的。

//双向循环链表的删除 e是删除的值
void deleteLinkList(LinkList *L,int place,ElemType e){
    if((*L) == NULL || place < 1){
        return;
    }
    LinkList target = (*L);
    
    //如果删除到只剩下首元结点了,则直接将*L置空;
    if(target->next == *L){
        free(*L);
        (*L) = NULL;
        return;
    }
    
    int index = 0;
    //找到需要删除的位置的结点
    while (index < place && target->next != *L) {
        target = target->next;
        index ++;
    }
    //这是超过边界的
    if(place > index){
        return;
    }
    
    LinkList deleteTemp = target;
    target->prior->next = deleteTemp->next;
    target->next->prior = target->prior;
    free(deleteTemp);
}

最后

线性表中的顺序表单链表单向循环链表双链表双向循环链表的操作就介绍完了。

上一篇 下一篇

猜你喜欢

热点阅读