线性表
线性表的特点如下:
- 表中元素个数有限
- 元素具有逻辑上的顺序性,表中元素有先后次序
- 表中元素都是数据元素,每个元素都是单个元素,元素的数据类型相同,意味着每个元素占有相同大小的存储空间
顺序表
特点:表中元素的逻辑顺序和物理顺序是相同的
实现代码
#ifndef MaxSize
#define MaxSize 50
typedef int ElementType;
//顺序表 以数组作为内部物理存储结构,大小固定
typedef struct
{
ElementType data[MaxSize];
int length;
}arrayNode;
arrayNode* InitList() {
arrayNode* l = (arrayNode*)malloc(sizeof(arrayNode));
l->length = 0;
for (ElementType i = 0; i < 30; i++) {
l->data[i] = i + 10;
l->length++;
}
return l;
}
ElementType length(arrayNode* L) {
return L->length;
}
bool Empty(arrayNode* L) {
return L->length == 0 ? true : false;
}
//按位查找
ElementType LocateElem(arrayNode* L, ElementType e) {
if (e<1 || e>L->length)
return -1;
return L->data[--e];
}
//按值查找
ElementType getElem(arrayNode* L, ElementType e) {
if (L->length == 0)
return -1;
int i = 0;
while (e != L->data[i]) {
i++;
}
return ++i;
}
//在指定位置i插入数据
bool ListInsert(arrayNode* L, ElementType e, ElementType i) {
if (i<1 || i>L->length+1)
return false;
if (L->length >= MaxSize)
return false;
if (i == L->length+1)
L->data[i-1] = e;
else {
for (ElementType s = L->length; s > i - 1; s--)
L->data[s] = L->data[s - 1];
L->data[i - 1] = e;
L->length++;
}
return true;
}
//在指定位置上删除数据
bool ListDelete(arrayNode* L, ElementType i) {
if (i<1 || i>L->length)
return false;
if (L->length == 0)
return false;
for (ElementType s = i; s < L->length; s++)
L->data[s - 1] = L->data[s];
L->length--;
return true;
}
//打印链表
void printList(arrayNode* L) {
for (ElementType i = 0; i < L->length; i++)
printf("%d\n", L->data[i]);
}
//摧毁链表
void DestroyList(arrayNode* L) {
if (L == NULL)
return;
free(L);
}
链表
以下所有代码全部都添加了头节点即链表的head指针域不存放数据,引入头节点具有以下好处:
- 由于第一个数据节点的位置被存放在头结点的指针域中,所以引入头节点可以使链表的第一个位置上的操作与其他位置操作一致,无需特殊处理。
- 无论链表是否为空,头指针指向了头结点的非空指针,因此空表和非空表的处理也得到了统一。
单链表
线性表的链式存储又称单链表,通过一组任意的存储单元来存储线性表的数据元素。
为了建立线性关系,除了存放数据元素外还会存放一个指向后继节点的指针
实现代码
#ifndef MaxSize
#define MaxSize 50
typedef int ElementType;
typedef struct _node
{
ElementType data;
struct _node* next;
}Node;
typedef struct
{
//增加一个尾节点,能够增加插入的效率
Node* tail;
Node* head;
int length;
}linkNode;
#include "singleLinkList.h"
#include <stdlib.h>
//单向链表,保存了头节点
//使用尾插法,速度快每次为O(1)
/*
linkNode* InitList() {
//给链表体申请空间
linkNode* l = (linkNode*)malloc(sizeof(linkNode));
//申请失败则异常退出程序
if (l == NULL)
exit(0);
//设置初始长度为0,同时申请一个空的表头
l->length = 0;
l->head = (Node*)malloc(sizeof(Node));
l->head->data = 0;
l->head->next = NULL;
l->tail = l->head;
//为这个链表设置三十个节点
for (ElementType i = 0; i < 30; i++) {
//临时申请一个变量为头节点方便操作
Node* temp = (Node*)malloc(sizeof(Node));
temp->data = i + 10;
temp->next = NULL;
l->tail->next = temp;
l->tail = l->tail->next;
l->length++;
}
return l;
}
*/
//使用头插法
//使用这个方法创建的链表每次都是和插入顺序倒叙的但是时间复杂度也是O(1)
linkNode* InitList() {
//给链表体申请空间
linkNode* l = (linkNode*)malloc(sizeof(linkNode));
//申请失败则异常退出程序
if (l == NULL)
exit(0);
//设置初始长度为0,同时申请一个空的表头
l->length = 0;
l->head = (Node*)malloc(sizeof(Node));
l->head->data = 0;
l->head->next = NULL;
//为这个链表设置三十个节点
for (ElementType i = 0; i < 30; i++) {
//临时申请一个变量为头节点方便操作
Node* temp = (Node*)malloc(sizeof(Node));
temp->data = i + 10;
temp->next = l->head->next;
l->head->next = temp;
l->length++;
}
return l;
}
//得到链表长度
ElementType length(linkNode* L) {
return L->length;
}
bool Empty(linkNode* L) {
return L->length == 0 ? true : false;
}
//按位查找
ElementType LocateElem(linkNode* L, ElementType e) {
if (e<1 || e>L->length)
return -1;
Node* temp = L->head;
for (ElementType i = 0; i < e; i++) {
temp = temp->next;
}
return temp->data;
}
//按值查找
Node* getElem(linkNode* L, ElementType e) {
if (L->length == 0)
return NULL;
Node* temp = L->head;
ElementType i;
for (i = 1;temp->data!=e; i++) {
temp = temp->next;
}
return temp;
}
//在指定位置i插入数据
bool ListInsert(linkNode* L, ElementType e, ElementType i) {
if (i<1 || i>L->length+1)
return false;
//不能超过最大长度50
if (L->length >= MaxSize)
return false;
Node* newNode = (Node*)malloc(sizeof(Node));
newNode->data = e;
//如果是最后一位在末尾插入
if (i == L->length + 1) {
newNode->next = NULL;
L->tail->next = newNode;
L->tail = newNode;
}
else {
Node* temp = L->head;
//我们要插入到第i个位置上,那么我们就要找到第i-1位置的数据然后插入在这个i-1位置后的位置
for (ElementType s = 0; s < i - 1; s++) {
temp = temp->next;
}
newNode->next = temp->next;
temp->next = newNode;
}
L->length++;
return true;
}
//在指定位置上删除数据
bool ListDelete(linkNode* L, ElementType i) {
if (i<1 || i>L->length)
return false;
if (L->length == 0)
return false;
Node* temp = L->head;
//找到这个位置的上一个节点,让上一个节点的next等于这个节点的next节点;
for (ElementType s = 0; s < i-1; s++) {
temp = temp->next;
}
Node* destenateNode = temp->next;
temp->next = destenateNode->next;
free(destenateNode);
L->length--;
return true;
}
//打印链表
void printList(linkNode* L) {
Node* temp = L->head;
for (ElementType i = 0; i < L->length; i++) {
printf("%d\n", temp->next->data);
temp = temp->next;
}
}
//摧毁链表
void DestroyList(linkNode* L) {
if (L == NULL)
return;
Node* temp = L->head;
Node* deTemp;
while (temp != NULL) {
deTemp = temp;
temp = temp->next;
free(deTemp);
}
free(L);
}
双向链表
在单链表中只有一个指向后继节点的指针,每次访问元素都只能从头到尾依次顺序访问,双向链表在每个结点添加了一个pre结点指向前置结点,克服了上述缺点。
实现代码
#ifndef MaxSize
#define MaxSize 50
//双向链表
typedef int ElementType;
typedef struct node {
ElementType data;
struct node* pre;
struct node* next;
}Node;
typedef struct bothWayLinkList {
Node* head;
Node* tail;
int length;
}bothWayList;
//双向链表,保存了头节点
//使用尾插法,速度快每次为O(1)
bothWayList* InitList() {
//给链表体申请空间
bothWayList* l = (bothWayList*)malloc(sizeof(bothWayList));
//申请失败则异常退出程序
if (l == NULL)
exit(0);
//设置初始长度为0,同时申请一个空的表头
l->length = 0;
l->head = (Node*)malloc(sizeof(Node));
l->head->next = NULL;
l->head->pre = NULL;
l->head->data = 0;
l->tail = l->head;
//为这个链表设置三十个节点
for (ElementType i = 0; i < 30; i++) {
Node* temp = (Node*)malloc(sizeof(Node));
temp->data = i + 10;
temp->next = NULL;
l->tail->next = temp;
temp->pre = l->tail;
l->tail = l->tail->next;
l->length++;
}
return l;
}
//使用头插法
//使用这个方法创建的链表每次都是和插入顺序倒叙的但是时间复杂度也是O(1)
/*
bothWayList* InitList() {
//给链表体申请空间
bothWayList* l = (bothWayList*)malloc(sizeof(bothWayList));
//申请失败则异常退出程序
if (l == NULL)
exit(0);
//设置初始长度为0,同时申请一个空的表头
l->length = 0;
l->head = (Node*)malloc(sizeof(Node));
l->head->data = 0;
l->head->next = NULL;
l->head->pre = NULL;
//为这个链表设置三十个节点
for (ElementType i = 0; i < 30; i++) {
Node* temp = (Node*)malloc(sizeof(Node));
temp->data = i + 10;
temp->next = l->head->next;
temp->pre = l->head;
l->head->next = temp;
l->tail = temp->next;
l->length++;
}
return l;
}
*/
//得到链表长度
ElementType length(bothWayList* L) {
return L->length;
}
bool Empty(bothWayList* L) {
return L->length == 0 ? true : false;
}
//按位查找
ElementType LocateElem(bothWayList* L, ElementType e) {
if (e<1 || e>L->length)
return -1;
Node* temp = L->head;
for (ElementType i = 0; i < e; i++) {
temp = temp->next;
}
return temp->data;
}
//按值查找
Node* getElem(bothWayList* L, ElementType e) {
if (L->length == 0)
return NULL;
Node* temp = L->head;
ElementType i;
for (i = 1; temp->data != e; i++) {
temp = temp->next;
}
if (temp == NULL)
return temp;
}
//在指定位置i插入数据
bool ListInsert(bothWayList* L, ElementType e, ElementType i) {
if (i<1 || i>L->length + 1)
return false;
//不能超过最大长度50
if (L->length >= MaxSize)
return false;
Node* temp = L->head;
//我们要插入到第i个位置上,那么我们就要找到第i-1位置的数据然后插入在这个i-1位置后的位置
for (int s = 0; s < i - 1; s++) {
temp = temp->next;
}
Node* newNode = (Node*)malloc(sizeof(Node));
newNode->data = e;
newNode->next = temp->next;
temp->next->pre = newNode;
newNode->pre = temp;
temp->next = newNode;
L->length++;
return true;
}
//在指定位置上删除数据
bool ListDelete(bothWayList* L, ElementType i) {
if (i<1 || i>L->length)
return false;
if (L->length == 0)
return false;
Node* temp = L->head;
//找到这个位置的上一个节点,让上一个节点的next等于这个节点的next节点;
for (ElementType s = 0; s < i - 1; s++) {
temp = temp->next;
}
Node* destenateNode = temp->next;
temp->next = destenateNode->next;
destenateNode->next->pre = temp;
free(destenateNode);
L->length--;
return true;
}
//打印链表
void printList(bothWayList* L) {
Node* temp = L->head;
Node* temp2 = L->tail;
for (int i = 0; i < L->length; i++) {
printf("%d\n", temp->next->data);
temp = temp->next;
}
for (int i = 0; i < L->length; i++) {
printf("%d\n", temp2->data);
temp2 = temp2->pre;
}
}
//摧毁链表
void DestroyList(bothWayList* L) {
if (L == NULL)
return;
Node* temp = L->head;
Node* deTemp;
while (temp != NULL) {
deTemp = temp;
temp = temp->next;
free(deTemp);
}
free(L);
}
双向循环列表
普通链表只能从头到尾遍历链表的节点,但是循环链表可以从整个链表的各个节点开始遍历链表的结点。
实现代码
#ifndef MaxSize
#define MaxSize 50
typedef int ElementType;
typedef struct _node
{
ElementType data;
struct _node* next;
struct _node* pre;
} Node;
typedef struct
{
//增加一个尾节点,能够增加插入的效率
Node* tail;
Node* head;
int length;
}doubleCircleLinkList;
//使用头插法
doubleCircleLinkList* InitList() {
//给链表体申请空间
doubleCircleLinkList* l = (doubleCircleLinkList*)malloc(sizeof(doubleCircleLinkList));
//申请失败则异常退出程序
if (l == NULL)
exit(0);
//设置初始长度为0,同时申请一个空的表头
l->length = 0;
l->head = (Node*)malloc(sizeof(Node));
l->head->data = 0;
l->head->next = NULL;
l->head->pre = NULL;
l->tail = l->head;
//为这个链表设置三十个节点
for (ElementType i = 0; i < 30; i++) {
//为新的节点开辟空间存储
Node* temp = (Node*)malloc(sizeof(Node));
temp->data = i + 10;
l->tail->next = temp;
temp->pre = l->tail;
l->tail = temp;
l->tail->next = l->head;
l->head->pre = l->tail;
l->length++;
}
return l;
}
//得到链表长度
ElementType length(doubleCircleLinkList* L) {
return L->length;
}
bool Empty(doubleCircleLinkList* L) {
return L->length == 0 ? true : false;
}
//按位查找
ElementType LocateElem(doubleCircleLinkList* L, ElementType e) {
if (e<1 || e>L->length)
return -1;
Node* temp = L->head;
for (ElementType i = 0; i < e; i++) {
temp = temp->next;
}
return temp->data;
}
//按值查找
Node* getElem(doubleCircleLinkList* L, ElementType e) {
if (L->length == 0)
return NULL;
Node* temp = L->head;
ElementType i;
for (i = 1; temp->data != e; i++) {
temp = temp->next;
}
return temp;
}
//在指定位置i插入数据
bool ListInsert(doubleCircleLinkList* L, ElementType e, ElementType i) {
if (i<1 || i>L->length + 1)
return false;
//不能超过最大长度50
if (L->length >= MaxSize)
return false;
Node* newNode = (Node*)malloc(sizeof(Node));
newNode->data = e;
//如果插入的位置是最后一个位置的话就直接在表尾添加
if (i == L->length + 1) {
L->tail->next = newNode;
newNode->pre = L->tail;
L->tail = newNode;
L->tail->next = L->head;
L->head->pre = L->tail;
}
else {
//其余位置就采用普通的单链表插入方法
Node* temp = L->head;
//我们要插入到第i个位置上,那么我们就要找到第i-1位置的数据然后插入在这个i-1位置后的位置
for (ElementType s = 0; s < i - 1; s++) {
temp = temp->next;
}
newNode->next = temp->next;
newNode->pre = temp;
temp->next->pre = newNode;
temp->next = newNode;
}
L->length++;
return true;
}
//在指定位置上删除数据
bool ListDelete(doubleCircleLinkList* L, ElementType i) {
if (i<1 || i>L->length)
return false;
if (L->length == 0)
return false;
Node* temp = L->head;
//找到这个位置的上一个节点,让上一个节点的next等于这个节点的next节点;
for (ElementType s = 0; s < i - 1; s++) {
temp = temp->next;
}
Node* destenateNode = temp->next;
temp->next = destenateNode->next;
destenateNode->next->pre = temp;
free(destenateNode);
L->length--;
return true;
}
//打印链表
void printList(doubleCircleLinkList* L) {
Node* temp = L->head;
for (ElementType i = 0; i < L->length + 1; i++) {
printf("%d\n", temp->next->data);
temp = temp->next;
}
}
//摧毁链表
void DestroyList(doubleCircleLinkList* L) {
if (L == NULL)
return;
Node* temp = L->head->next;
Node* deTemp;
while (temp != L->head) {
deTemp = temp;
temp = temp->next;
free(deTemp);
}
free(L->head);
free(L);
}
顺序表和链表的比较
1.存取方式
顺序表可以顺序存储和随机存储,链表只能从表头顺序存取元素
2.逻辑结构和物理结构
采用顺序表存储,逻辑上相邻的元素,在物理上的存储位置也相邻。而采用链式存储时,逻辑相邻的元素物理存储位置往往不相邻。
3.空间分配
顺序表在静态存储分配时,一旦存储空间满了就不能扩充,继续存储会导致内存溢出。因此预分配需要足够大,但是如果过大会导致内存浪费。动态存储分配可以分配,但需要大量的移动元素,操作效率很低,而且可能内存中没有如此大的连续存储空间,导致分配失败,但是链式存储就不会出现这些问题,只要内存有空间就可以分配
顺序表和链表的选取策略
- 难以估计存储规模采取链式存储。
- 建表后需要频繁的插入删除操作时链表的性能更好,选取链表。但是如果需要频繁的查找元素,顺序表更佳
- 顺序表更容易实现,逻辑上相对简单。
如有错误欢迎留言指正,非常感谢