数据结构-线性表

image

线性表

由n(n>=0)个数据特性相同的元素构成的有限序列,称为线性表。他是最基本、最简单、也是最常用的一种数据结构。线性表结构中,数据元素之间通过一对一首尾相接的方式连接起来。

特点:

  1. 存在唯一一个被称为“第一个”的数据元素
  2. 存在唯一一个被成为“最后一个”的数据元素。
  3. 除了第一个元素以外,结构中的每一个元素均只有一个前驱
  4. 除了最后一个元素以外,结构中的每个元素均只有一个后继

1.顺序表

使用一段物理地址连续的存储单元一次存储数据元素的顺序存储结构顺序映像,被称为顺序表(Sequential List)。

1.1 特点

  • 存在唯一的一个被称作“第一个”和“最后一个”的数据元素。
  • 除第一个和最后一个之外,其余元素有且仅有一个直接先驱和一个直接后继。
  • 数据元素直接的逻辑关系是“一对一”

优点:

  1. 逻辑上相邻的元素,物理位置也相邻。
  2. 可随机访问

缺点:

  1. 插入,删除操作需要移动大量的元素,效率低
  2. 需要预先分配足够大的储存空间,占据空间大

1.2 静态、动态分配

静态: 定义数组的时候,指定了输出长度

区别:定义elem时,静态使用的是数组,而动态使用的是指针,然后通过动态分配存储空间函数malloc或者new来分配一块数组空间,并将起始地址赋给elem,指针elem就会指向这个空间。

// 静态
# define MAXSIZE 100

typedef struct {
    ElemType elem[MAXSIZE];			// ElemType 是为了描述统一而自定的
    int length;
} SqList;
SqList L;

// 动态
typedef struct {
    ElemType *elem;
    int length;
} SqList;

1.3 接口实现

头文件SeqList.h,接下来的内容在SeqList.c中实现。

#pragma once		// 防止被二次引用

#ifndef SEQLIST_SEQLIST_H
#define SEQLIST_SEQLIST_H

#endif //SEQLIST_SEQLIST_H

#pragma once
#include "stdio.h"
#include "assert.h"
#include "stdlib.h"

typedef int DataType;
typedef struct  {
    DataType *elem;
    int length;
    int capacity;
} SeqList;

void Init(SeqList *l);							// 初始化
void Destroy(SeqList *l);						// 销毁
void PrintList(SeqList *l);						// 打印顺序表
void PushBack(SeqList *l, DataType x);			// 尾插
void PopBack(SeqList *l);						// 尾删
void FrontPop(SeqList *l);						// 头删
void FrontPush(SeqList *l, DataType x);			// 头插
int Find(SeqList *l, DataType x);				// 查找指定值是否存在
void Insert(SeqList *l, int pos, DataType x);	// 指定位置插入数据
int ShowLength(SeqList *l);						// 返回顺序表长度
初始化顺序表

初始化其实就是创建一个空的顺序表,断言防止传入空指针。

void InitSeqList(SeqList *l) {
    assert(l != NULL);
    
    l->elem = NULL;
    l->length = 0;
    l->capacity = 0;
    
    // 如果采用静态的话就是这样:
    // l->elem = (DataType*)malloc(MAXSIZE*sizeof(DataType));
    // l->length = 0;
    // l->capacity = MAXSIZE;
}
销毁
void Destroy(SeqList *l) {
    assert(l!=NULL);
    
    free(l->elem);
    l->length = 0;
    l->elem = NULL;
}
扩容

当顺序表的容量满了之后,一次次增加空间太过于麻烦且消耗的代价很大,通常会选择一次性增容 2倍,2倍只是一个折中的数,为了不造成空间的浪费。

void CheckCapacity(SeqList *l) {
    assert(l != NULL);
    
    if (l->length <span style="font-weight: bold;" class="mark"> l->capacity) {
        int newCapacity;
        if (l->capacity </span> 0) {
            newCapacity = l->capacity = 4;		// 原来为0,扩充为4
        } else {
            newCapacity = 2*l->capacity;		// 原来不为0,扩充为2倍
        }
        DataType *x = (DataType*)realloc(l->elem, newCapacity*sizeof(DataType));
        if (x == NULL) {
            perror("realloc");
            exit(-1);
        }
        l->elem = x;		// x不为空,则说明开辟成功
        l->capacity = newCapacity;		// 更新容量
    }
}
打印顺序表

断言之后要先判断是否为空。

void PrintList(SeqList *l){
    assert(l!=NULL);
    if (l->lenth == 0) {
        printf("The seqList is null");
        return;
    }
    
    int i;
    for (i = 0; i < l->length; i++) {
        printf(" %d", l->elem[i]);
    }
    printf("\n");
}
尾插
void PushBack(SeqList *l, DataType x) {
    assert(l != NULL);
    CheckCapacity(l);	// 插入之前先检查顺序表是否容量已经满了
	
    l->elem[l->length] = x;
    l->length++;
}
尾删
void PopBack(SeqList *l) {
    assert(l != NULL);
    if (l->length == 0) {
        printf("the list is empty, delete from back is invalid");
        return;
    }
    
    // 因为不知道这个DataType是什么类型,不能贸然赋值为0
    // l->elem[l->length-1] = 0;
    l->length--;
}
头插
void FrontPush(SeqList *l, DataType x){
    assert(l != NULL);
    CheckCapacity(l);	// 插入之前先检查顺序表是否容量已经满了
    
    int i;
    for (i = l->length-1; i >= 0; i--) {
		l->elem[i+1] = l->elem[i]; 
    }
    l->elem[0] = x;
    l->length++;
}
头删
void FrontPop(SeqList *l) {
     assert(l != NULL);
     assert(l->length > 0);
    
    int i;
    for (i = 1; i < l->length-1; i++) {
        l->elem[i-1] = l->elem[i];
    }
    l->length--
}
查找指定值是否存在
int Find(SeqList *l, DataType x){
    assert(l != NULL);
    assert(l->length > 0);
    
    int i;
    for (i= 0; i < l->length; i++) {
        if (l->elem[i] == x) {
            return i;
        }
    }
    return -1;
}
在指定位置插入数据
void Insert(SeqList *l, int pos, DataType x) {
    assert(l != NULL);
    assert(l->length > 0);
    assert(pos >= 0 && pos <= l->length);		// 传入的pos必须合法
    
    CheckCapacity(l);	// 插入之前先检查顺序表是否容量已经满了
    
    int i;
    for (i = l->length; i > pos; i--) {
        l->elem[i] = l->elem[i-1]
    }
    l->elem[pos] = x; 
    l->length++;
}
删除指定位置的数据
void Delete(SeqList *l, int pos) {
    assert(l != NULL);
    assert(pos >= 0 && pos <= l->length);
    
	int i;
    for (i = pos; i < length-1; i++) {
		l->elem[i-1] = l->elem[i];
    }
    l->length--;
}
返回顺序表长度

在数据结构中有一个约定,如果要访问或修改数据结构中的数据,不要直接去访问。通过函数来访问和修改更加的规范和安全,也方便检查是否出现了越界等等报错情况。

int Find(SeqList *l, DataType x) {
	assert(l != NULL);
    
	return l->length;
}
修改指定下标位置的数据
void Alter(SeqList *l, int pos, DataType x) {
    assert(l != NULL);
    assert(l->length > 0);
    assert(pos >= o && pos < l->length);
    
    l->elem[pos] = x; 
}

因此头插法那里可以这样调用:

void FrontPush(SeqList *l, DataType x) {
	Alter(l, 0, x);
}
判空
bool IsEmpty(SeqList *l) {
    return l->length <span style="font-weight: bold;" class="mark"> 0 ? true : false;
}

2.链表

一种物理存储结构上不连续的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的,可以实现更加灵活的动态内存管理。

链表的结点由两部分组成:

数据域:存放数据元素信息

指针域:存放后继或前驱的地址

  • 链表可分为:

单链表:只包含一个指针域next,指向后继

双链表:包含两个指针域:next指向后继

2.1 单链表

2.1.1 存储结构

最后一个结点的指针为空(NULL),设置一个头指针H指向第一个结点,通常在第一个结点之前附设一个结点,称为头结点。

首元结点,是指链表中存储的第一个数据元素a1的结点,

头结点,是首元结点之前附设的一个结点,其指针域指向首元结点,可以不储存任何信息

头指针,是指向链表中第一个结点的指针,若有头结点则指向头结点,无则指向首元结点

要访问单链表中任一元素必须从头指针出发,故单链表是“顺序存取”。单链表可由头指针唯一确定==(也就是说,一个单链表可简单理解成是一个头指针,而头指针就是结点指针,故单链表类型是结点指针类型)

2.1.2 接口实现

定义头文件SinglyLinkedList.h

typedef int DataType;

typedef struct SListNode {
    DataType data;
    struct SListNode *next;
} SListNode;

SListNode* ApplyNode(DataType x);
动态申请一个节点
SListNode* ApplyNode(DataType x) {
	SListNode* node = (SListNode*)malloc(sizeof(SListNode));
    if (node == NULL) {
        perror("malloc");
        return;
    }
    node->data = x;
    node->next = NULL;
    
    return node;
}
销毁
// **pHead是一个指向单链表头结点指针的指针
void Destroy(SListNode **pHead) {
    assert(pHead);

    // cur指向当前要释放的节点,即单链表的头结点
    SListNode *cur = *pHead;
    while(cur != NULL) {
        // 指向cur的下一个节点
        SListNode *next = cur->next;
        free(cur);
        cur = next;
    }
    *pHead = NULL;
}
打印
void PrintList(SListNode *phead) {
    while (*phead != NULL) {
        printf("%d->", *phead->data);
        *phead = *phead->next;
    }
    printf("NULL\n");
}
头插
void FrontPush(SListNode **ppHead, DataType x) {
	assert(ppHead);
    assert(*ppHead);
  
    SListNode *newnode = ApplyNode(x);
  
    newnode->next = *ppHead;
    *ppHead = newcode;
}
头删
void FrontPop(SListNode **pphead) {
    assert(*pphead);
    
    SListNode *cur = *pphead;
    *pphead = cur->next;
    free(cur);
}
尾插
void BackPush(SListNode **pphead, DataType x) {
    assert(pphead);

    SListNode *newnode = ApplyNode(x);
    if (*pphead == NULL) {
		*pphead = newnode;
    } else {
        SListNode *tail = *pphead;
        while (tail->next != NULL) {
            tail = tail->next;
        }
        tail->next = newnode;
    }
}
尾删
void BackPop(SListNode **pphead) {
    assert(pphead);
    assert(*pphead);
    
    SListNode *tail = *pphead;
    if ((*pphead) == NULL) {
        free(*phead);
        *phead = NULL;
    } else {
		SListNode *prev = *pphead;
        while (tail->next) {
            prev = tail;
            tail = tail->next;
        }
        prev->next = NULL;
        free(tail);
    }
}
查找相应的数据
SListNode* Find(SListNode *phead, DataType x) {
	SListNode *ptr = phead;
    while (ptr != NULL) {
		if (ptr->data == x) {
            return ptr;
        }
        ptr = ptr->next;
    }
    return NULL;
}
在指定位置之后插入
void InsertAfter(SListNode *position, DataType x) {
	SListNode *newnode = ApplyNode(x);
    
    newnode->next = position->next;
    postion->next = newnode;
}

void Insert(SListNode **pphead, DataType x) {
    SListNode *position = Find(*pphead, 4);
    if (position) {
		InsertAfter(position, x);
    } else {
        printf("Number provided not found!\n");
    }
}
删除指定位置的节点
void Delete(SListNode **pphead, SListNode *position) {
    assert(pphead);
    assert(*pphead);
    assert(position);
    
    if (position == *pphead) {
        // 说明是在首部,直接采用头删法即可
        FrontPop(pphead);
    } else {
        // 否则为中间结点
        SListNode *prev = *pphead;
        while (prev->next != position) {
            prev = prev->next;
        }
        prev->next = position->next;
        free(position);
        position = NULL;	// 置空以避免悬空指针,防止意外的程序崩溃
    }
}
删除指定位置之后的节点
void DeleteAfter(SListNode *position) {
    assert(position);
    assert(position->next);		// 不能为尾结点
    
    SListNode *behind = position->next;
    position->next = behind->next;
    free(behind);
    behind = NULL;
}
求链表的长度
int GetLength(SListNode *pphead) {    
    SListNode *ptr = pphead;
    int counter = 0;
   	while (ptr != NULL) {
		ptr = ptr->next;
        counter++;
    }
    return counter;
}
判空
bool IsEmpty(SListNode *phead) {
	return phead <span style="font-weight: bold;" class="mark"> NULL;
    
    // 第二种写法
    // return phead </span> NULL ? true : false;
}

所有的测试代码

void test2() {
    SListNode *plist = NULL;

    BackPush(&plist, 1);
    BackPush(&plist, 4);
    BackPush(&plist, 5);
    BackPush(&plist, 7);
    BackPop(&plist);
    FrontPop(&plist);
    FrontPush(&plist, 12);
    if (Find(plist, 12) != NULL) {
        printf("Find it!\n");
    } else {
        printf("Number not found!\n");
    }
    Insert(&plist, 99);

    Delete(&plist, Find(plist, 4));
    DeleteAfter(Find(plist, 99));

    PrintList(plist);
    printf("length for singleLinkedList: %d", GetLength(plist));
    Destroy(plist);
}

2.2 双链表

posted @ 2024-03-06 18:33  lockly  阅读(45)  评论(0编辑  收藏  举报