数据结构--》从线性表说起,掌握常用基础算法

这篇具有很好参考价值的文章主要介绍了数据结构--》从线性表说起,掌握常用基础算法。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

初识线性表

线性表的基本操作

顺序表的定义

顺序表的基本操作

单链表的定义

单链表的基本操作 

双链表的介绍

循环链表的介绍

静态链表的介绍


初识线性表

线性表是具有相同数据类型的 n (n0) 个数据元素的有限序列,其中n为表长,当n=0时线性表是一个空表。若用L命名线性表,则其一般表示为:

数据结构--》从线性表说起,掌握常用基础算法

是线性表中的第 “i” 个元素线性表中的位序;是表头元素;是表尾元素

除第一个元素外,每个元素有且仅有一个直接前驱;除最后一个元素外,每个元素有且仅有一个直接后继

线性表的基本操作

实际开发中,可根据实际需求定义其他的基本操作,对数据的操作(记忆思路)为:创销增删改查,函数名和参数的形式都可以改变。那么我们为什么要实现对数据结构的基本操作呢

1)团队合作编程,你定义的数据结构要让别人能够很方便的使用(封装)

2)将常用的操作/运算封装成函数,避免重复工作,降低出错风险。

InitList($L):初始化表。构造一个空的线性表L,分配内存空间。

DestroyList(&L):销毁线性表。销毁操作并释放线性表L所占用的内存空间。

ListInsert(&L,i,e):插入操作。在表L中的第i个位置插入指定元素e。

ListDelete(&L,i,e):删除操作。删除表L中第i个位置的元素,并用e返回删除元素的值。

LocateElem(L,e):按值查找操作。在表L中查找具有给定关键字值的元素。

GetElem(L,i):按位查找操作。获取表L中第i个位置的元素的值。

Length(L):求表长。返回线性表L的长度,即L中数据元素的个数。

PrintList(L):输出操作。按前后顺序输出线性表L的所有元素值。

Empty(L):判空操作。若L为空表,则返回true,否则返回false。

当我们对参数的修改结果需要带过来时,需要传入引用 “&” 。给出如下代码进行解释:

#include <stdio.h>
void test(int x) {
	x = 1024;
	printf("test函数内部 x=%d\n", x);
}

int main() {
	int x = 1;
	printf("调用test前 x=%d\n", x);
	test(x);
	printf("调用test后 x=%d\n", x);
}

控制台打印的结果如下, 其实test函数里的x是main函数里面x的复制品,两者是两种不同内存地址的数据,test仅仅是改变了其内容x的数据,并没有改变main函数中x的数据,说白了就是main函数的x调用test函数并没有对参数的修改结果带回来,所有结果依然是1。

数据结构--》从线性表说起,掌握常用基础算法

如果我们在test函数中采用引用类型,test函数对x的修改就会带回到main函数当中,如下:

数据结构--》从线性表说起,掌握常用基础算法

回顾重点,其主要内容整理成如下内容:

数据结构--》从线性表说起,掌握常用基础算法

顺序表的定义

顺序表是用顺序存储的方式实现线性表顺序存储。把逻辑上相邻的元素存储在物理位置上也相邻的存储单元中,元素之间的关系由存储单元的邻接关系来体现。

顺序表的静态分配实现(顺序表的表长刚开始确定就无法更改)

#define MaxSize 10			// 定义最大长度
typedef struct {			
	ElemType data[MaxSize]; // 用静态的“数组”存储数据元素
	int length;				// 顺序表的当前长度
}SqList;					// 顺序表的类型定义

顺序表的动态分配实现

#define InitSize 10			// 顺序表的初识长度
typedef struct {			
	ElemType *data;		    // 指示动态分配数组的指针
	int MaxSize;			// 顺序表的最大容量
	int length;				// 顺序表的当前长度
}SeqList;					// 顺序表的类型定义(动态分配方式)

顺序表的特点

1)随机访问,即可以在O(1)时间内找到第i个元素。

2)存储密度不高,每个节点只存储数据元素

3)拓展容量不方便(即便采用动态分配的方式实现,拓展长度的时间复杂度也比较高)

4)插入、删除操作不方便,需要移动大量元素

回顾重点,其主要内容整理成如下内容:

数据结构--》从线性表说起,掌握常用基础算法

顺序表的基本操作

插入操作:ListInsert(&L,i,e):在表L中的第i个位置上插入指定元素e。其基础代码如下:

#include <stdio.h>

#define MaxSize 10			// 定义最大长度
typedef struct {			
	int data[MaxSize]; // 用静态的“数组”存储数据元素
	int length;				// 顺序表的当前长度
}SqList;					// 顺序表的类型定义

bool ListInsert(SqList& L, int i, int e) {
	if (i<1 || i>L.length + 1) { // 判断i的范围是否有效
		return false;
	}
	if (L.length >= MaxSize) { // 当前的存储空间已满,不能插入
		return false;
	}
	for (int j = L.length; j >= i; j--){ // 将第i个元素及之后的元素后移
		L.data[j] = L.data[j - 1];
	}
	L.data[i - 1] = e; // 在位置i处存放e
	L.length++; // 长度+1
	return true;
}

int main() {
	SqList L; // 声明一个顺序表
	InitList(L); // 初始化顺序表
	// ...此处省略代码,插入几个元素
	ListInsert(L, 3, 3);
	return 0;
}

插入操作的时间复杂度分析

最好情况:新元素插入到表尾,不需要移动元素

                  i = n + 1,循环0次,最好的时间复杂度 = O(1)

最坏情况:新元素插入到表头,需要将原有的n个元素全都向后移动

                  i = 1,循环n次,最坏时间复杂度 = O(n)

平均情况:假设新元素插入到任何一个位置的概率相同,即i=1,2,3,..,length+1的概率都是p=数据结构--》从线性表说起,掌握常用基础算法。i=1,循环n次;i=2,循环n-1次;i=3,循环n-2次 ... i=n+1时,循环0次。

                 平均循环次数 = np + (n-1)p+...+1·p = 数据结构--》从线性表说起,掌握常用基础算法 = ,平均时间复杂度 = O(n)

删除操作:ListDelete(&L,i,&e):删除表L中的第i个位置的元素,并用e返回删除元素的值。

bool ListDelete(SqList &L, int i, int &e){
	if (i<1 || i>L.length) { // 判断i的范围是否有效
		return false;
	}
	e = L.data[i - 1]; // 将被删除的元素赋值给e
	for (int j = i; j < L.length; j++){ // 将第i个元素及之后的元素前移
		L.data[j-1] = L.data[j];
	}
	L.length--; // 线性表长度减1
	return true;
}

int main() {
	SqList L; // 声明一个顺序表
	InitList(L); // 初始化顺序表
	// ...此处省略代码,插入几个元素
	int e = -1; // 用变量e把删除的元素“带回来”
	if (ListDelete(L, 3, e)) {
		printf("已删除第3个元素,删除元素值为=%d\n", e);
	}
	else {
		printf("位序i不合法,删除失败\n");
	}
	return 0;
}

删除操作的时间复杂度分析

最好情况:删除表尾元素,不需要移动其他元素。

                  i=n,循环0次;最好时间复杂度 = O(1)

最坏情况:删除表头元素,需要将后续的n-1个元素全都向前移动

                  i=1,循环n-1次;最坏时间复杂度 = O(n)

平均情况:假设删除任何一个元素的概率相同,即i=1,2,3,..,length的概率都是p=,i=1,循环n-1次;i=2时,循环n-2次;i=3时,循环n-3次...i=n时,循环0次

                  平均循环次数=(n-1)p+(n-2)p+...+1·p=,平均时间复杂度=O(n)

回顾重点,其主要内容整理成如下内容:

数据结构--》从线性表说起,掌握常用基础算法

按位查找: GetElem(L,i):获取表L中第i个位置的元素的值。

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

由于顺序表的各个数据元素在内存中连续存放,因此可以根据起始地址和数据元素大小立即找到第i个元素——“随机存储”特性,因此其时间复杂度:O(1)。

按值查找:在表L中查找具有给定关键字值的元素。

int LocateElem(SeqList L, ElemType e) {
	for (int i = 0; i < L.length; i++) {
		if (L.data[i] == e) {
			return i + 1;// 数组下标为i的元素值等于e,返回其位序i+1
		}
	}
	return 0; // 退出循环,说明查找失败
}

按值查找操作的时间复杂度分析

最好情况:目标元素在表头

                  循环1次:最好时间复杂度=O(1)

最坏情况:目标元素在表尾

                  循环n次:最坏时间复杂度=O(n)

平均情况:假设目标元素出现在任何一个位置的概率相同,都是,目标元素在第1位,循环1次;在第2位,循环2次,...;在第n位,循环n次。

                  平均循环次数=1·+2·+3·+....+n· = 数据结构--》从线性表说起,掌握常用基础算法 = 数据结构--》从线性表说起,掌握常用基础算法,平均时间复杂度=O(n)

回顾重点,其主要内容整理成如下内容:

数据结构--》从线性表说起,掌握常用基础算法

单链表的定义

单链表每个节点除了存放数据元素之外,还要存储指向下一个节点的指针。其不要求大片连续空间,改变容量方便,但是不可随机存取,要耗费一定空间存放指针。

要声明一个单链表时,只需声明一个头指针L,指向单链表的第一个结点。

typedef struct LNode { // 定义单链表结点类型
	ElemType data; // 每个节点存放一个数据元素
	struct LNode* next; // 指针指向下一个节点
}LNode, *LinkList;

不带头节点的单链表

// 初始化一个空的单链表
bool InitList(LinkList& L) {
	L = NULL; // 空表,暂时没有任何节点
	return true;
}

带头节点的单链表

// 初始化一个单链表(带头节点)
bool InitList(LinkList& L) {
	L = (Lode * )malloc(sizeof(LNode)); // 分配一个头节点
	if (L == NULL) // 内存不足,分配失败
		return false;
	L->next = NULL; // 头节点之后暂时还没有节点
	return true;
}

回顾重点,其主要内容整理成如下内容: 

数据结构--》从线性表说起,掌握常用基础算法

单链表的基本操作 

插入操作:ListInsert(&L,i,e):在表L中的第i个位置上插入指定元素e。

按位序插入(带头节点)

数据结构--》从线性表说起,掌握常用基础算法

按位序插入(不带头节点)

数据结构--》从线性表说起,掌握常用基础算法

后插操作

数据结构--》从线性表说起,掌握常用基础算法

前插操作

数据结构--》从线性表说起,掌握常用基础算法

删除操作:ListDelete(&L,i,&e):删除表L中第i个位置的元素,并用e返回删除元素的值。

按位序删除(带头节点)

数据结构--》从线性表说起,掌握常用基础算法

按位查找:获取表L中第i个位置的元素的值。

数据结构--》从线性表说起,掌握常用基础算法

按值查找:在表L中查找具有给定关键字值的元素。

数据结构--》从线性表说起,掌握常用基础算法

回顾重点,其主要内容整理成如下内容: 

数据结构--》从线性表说起,掌握常用基础算法

尾插法建立单链表

数据结构--》从线性表说起,掌握常用基础算法

头插法建立单链表

数据结构--》从线性表说起,掌握常用基础算法

头插法尾插法:核心就是初始化操作、指定结点的后插操作。头插法的重要应用:链表的逆置。

双链表的介绍

双向链表(Doubly Linked List),也称双链表,是一种常见的线性数据结构,与单向链表类似,但每个节点有两个指针,一个指向前一个节点,一个指向后一个节点,因此可以双向遍历。

每个节点包含三个部分:数据域、指向前一个节点的指针域 prev 和指向后一个节点的指针域 next。第一个节点的 prev 指针一般为空,最后一个节点的 next 指针一般为空。

相比单向链表,双向链表在插入、删除等操作时更为灵活,同时可以双向遍历,方便查找前一个及后一个节点,因此在某些场景下使用双向链表更为方便快捷。但是,双向链表相对于单向链表需要额外的空间来存储指向前一个节点的指针,因此会占用更多的内存空间。

双链表的初始化(带头结点)

数据结构--》从线性表说起,掌握常用基础算法

双链表的插入

数据结构--》从线性表说起,掌握常用基础算法

双链表的删除

数据结构--》从线性表说起,掌握常用基础算法

双链表的遍历(双链表不可随机存取,按位查找、按值查找操作都只能用遍历方式实现)。

数据结构--》从线性表说起,掌握常用基础算法

回顾重点,其主要内容整理成如下内容:

数据结构--》从线性表说起,掌握常用基础算法

循环链表的介绍

循环链表(Circular Linked List)是一种特殊的链表结构,它的最后一个节点指向头节点,从而形成一个环形结构。循环链表可以是单向的或双向的。

循环单链表:循环单链表是一种只有一个指针域的单向链表,其最后一个节点的指针指向头节点,实现了链表的循环。下面是一个用C语言实现循环单链表的基本代码:

// 单向循环链表结点定义
typedef struct ListNode {
    int val;
    struct ListNode *next;
} ListNode;

// 创建循环单链表
ListNode* createCircularLinkedList(int data[], int n) {
    ListNode *head = (ListNode*)malloc(sizeof(ListNode));
    head->val = 0;  // 头结点不存储数据
    head->next = NULL;
    ListNode *tail = head;
    for (int i = 0; i < n; ++i) {
        ListNode *node = (ListNode*)malloc(sizeof(ListNode));
        node->val = data[i];
        node->next = NULL;
        tail->next = node;
        tail = node;
    }
    tail->next = head;  // 最后一个节点指向头节点,形成循环
    return head;
}

// 遍历循环单链表
void traverseCircularLinkedList(ListNode *head) {
    ListNode *p = head->next;
    while (p != head) {
        printf("%d ", p->val);
        p = p->next;
    }
}

// 删除循环单链表
void deleteCircularLinkedList(ListNode *head) {
    ListNode *p = head->next;
    while (p != head) {
        ListNode *temp = p;
        p = p->next;
        free(temp);
    }
    free(head);
}

循环双链表:循环双链表是一种具有两个指针域的链表,除了每个节点都有一个指向下一个节点的指针 next 外,还有一个指向前一个节点的指针 prev。它的最后一个节点的 next 指针指向头节点,头节点的 prev 指针指向最后一个节点,这样就形成了一个环形结构。下面是一个用C++实现循环双链表的基本代码:

// 双向循环链表结点定义
struct ListNode {
    int val;
    ListNode *prev, *next;
    ListNode(int x) : val(x), prev(NULL), next(NULL) {}
};

// 创建循环双向链表
ListNode* createCircularDoublyLinkedList(vector<int>& nums) {
    int n = nums.size();
    ListNode *head = new ListNode(0);  // 头结点不存储数据
    ListNode *tail = head;
    for (int i = 0; i < n; ++i) {
        ListNode *node = new ListNode(nums[i]);
        node->prev = tail;
        node->next = head;
        tail->next = node;
        head->prev = node;
        tail = node;
    }
    return head;
}

// 遍历循环双向链表
void traverseCircularDoublyLinkedList(ListNode *head) {
    ListNode *p = head->next;
    while (p != head) {
        cout << p->val << " ";
        p = p->next;
    }
}

// 删除循环双向链表
void deleteCircularDoublyLinkedList(ListNode *head) {
    ListNode *p = head->next;
    while (p != head) {
        ListNode *temp = p;
        p = p->next;
        delete temp;
    }
    delete head;
}

静态链表的介绍

静态链表(Static Linked List)是一种利用数组来模拟链表的数据结构,其本质上并不是真正的链表,而是通过数组和指针来实现链表的功能。在静态链表中,用数组来存储节点的数据元素,同时用另外一个数组来存储链表节点的指针。下面是一个用C++实现静态链表的基本代码:

const int N = 100000;

struct ListNode {
    int val, next;
} nodes[N];

// 根据数组创建静态链表
int createStaticLinkedList(int data[], int n) {
    for (int i = 0; i < n; ++i) {
        nodes[i].val = data[i];
        nodes[i].next = i + 1;
    }
    nodes[n - 1].next = -1;
    return 0;  // 返回头指针位置
}

// 向静态链表插入元素
// pos为需要插入的位置,val为要插入的元素值
void insertStaticLinkedList(int pos, int val) {
    int p = 0;  // p初始化为头指针
    while (pos--) p = nodes[p].next;  // 在pos-1的位置停留
    int q = 0;  // q初始化为空闲节点的位置
    while (nodes[q].next != -1) q = nodes[q].next;  // 找到最后一个空闲节点
    nodes[q].val = val;
    nodes[q].next = nodes[p].next;
    nodes[p].next = q;  // 将q插入在p之后
}

// 从静态链表删除元素
// pos为需要删除的位置
void deleteStaticLinkedList(int pos) {
    int p = 0;  // p初始化为头指针
    while (pos--) p = nodes[p].next;  // 在pos-1的位置停留
    int q = nodes[p].next;
    nodes[p].next = nodes[q].next;  // 将q从链表中删除
    nodes[q].next = -1;  // 将q加入到空闲节点链表中
}

静态链表优缺点

增删操作不需要大量移动元素。不能随机存取,只能从头结点开始依次往后查找,容量固定不变。

在实际应用中,使用静态链表需要注意以下问题

静态链表中的空闲节点需要预先分配好,不能像动态链表那样动态申请内存,否则会破坏静态链表的结构。

静态链表的节点数量是有限的,因为数组的大小是固定的,因此应该根据实际情况来设置数组的大小。

顺序表和链表的优缺点

顺序表

优点:存储密度高,因为在内存中是一段连续的空间存储数据,访问元素的时间复杂度为O(1),因此查找、修改等操作效率较高。支持下标访问元素,编程简单直观。

缺点:插入、删除元素时需要移动其他元素,时间复杂度为O(n),因此插入、删除操作效率相对较低。内存空间的分配和释放需要考虑到数组大小,因此当需要频繁的插入、删除操作时可能会浪费内存空间。

链表

优点:插入、删除元素时只需要修改指针指向,不需要移动其他元素,时间复杂度为O(1),因此插入、删除操作效率较高。内存空间的分配和释放更加灵活,可以根据实际需要动态分配。

缺点:存储密度较低,因为每个节点需要额外存储指针信息,占用内存空间较大。访问元素需要遍历整个链表,时间复杂度为O(n),因此查找、修改等操作效率相对较低。不支持下标访问元素,需要通过指针进行访问。

实现线性表时,使用顺序表还是链表好

当我们需要高效地进行查找、修改操作时,可以使用顺序表;当我们需要高效地进行插入、删除操作时,可以使用链表。如果需要频繁地进行插入、删除操作,并且元素数量不确定或者需要动态改变,则应该优先考虑使用链表。但是,顺序表的实现和使用比链表更为简单,因此当元素数量相对固定或者需要使用下标操作时,可以优先考虑使用顺序表。文章来源地址https://www.toymoban.com/news/detail-494294.html

到了这里,关于数据结构--》从线性表说起,掌握常用基础算法的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包赞助服务器费用

相关文章

  • 基础+常用的数据结构

    基础+常用的数据结构

    JDK,它是功能齐全的 Java SDK,是提供给开发者使用,能够创建和编译 Java 程序的开发套件。它包含了 JRE,同时还包含了编译 java 源码的编译器 javac 以及一些其他工具比如 javadoc(文档注释工具)、jdb(调试器)、jconsole(基于 JMX 的可视化监控⼯具)、javap(反编译工具)等等

    2024年01月22日
    浏览(19)
  • 【算法与数据结构】1 算法0基础入门,详解什么是算法?什么是线性查找法?

    【算法与数据结构】1 算法0基础入门,详解什么是算法?什么是线性查找法?

    欢迎来到爱书不爱输的程序猿的博客, 本博客致力于知识分享,与更多的人进行学习交流 本文收录于算法与数据结构体系专栏, 本专栏 是服务于0基础者,一起完成从0到1的跨越 就是 一系列 可以解决问题的 清晰的 可执行的 计算机指令 那么生活中有哪些算法? 问路 :坐公交车到

    2023年04月09日
    浏览(22)
  • 【C#基础】C# 常用数据结构

    序号 系列文章 4 【C#基础】C# 变量和常量的使用 5 【C#基础】C# 运算符总结 6 【C#基础】C# 常用语句讲解 😀大家好,我是writer桑,前面一章已经学习了 C# 中常用语句的用法,那本章就开始学习 C# 程序中 常用的 数据结构的介绍与用法,希望看完大家能够有所收获,感谢支持!

    2024年02月06日
    浏览(11)
  • Java基础---常用类大全以及各数据结构的方法大全

    Java基础---常用类大全以及各数据结构的方法大全

    目录 前言 一、Math类 二.Scanner类 三、String类、StringBuilder和StringBuffer类 💖String类 💖StringBuilder和StringBuffer 四.Arrays类 五.Random类 六.时间类 七.ArrayList顺序表 八、LinkedList与链表 九.Stack栈和Queue队列 十.PriorityQueue优先级队列,堆 🎁博主介绍:博客名为tq02,已学C语言、JavaSE,目

    2024年02月16日
    浏览(10)
  • 算法 数据结构分类 数据结构类型介绍 数据结构线性非线性结构 算法合集 (一)

     数据结构分为:                            a.线性结构                            b.非线性结构  a.线性结构:                       数据与结构存在一对一的线性关系; a . 线性结构 存储 分为:                                   顺序存储

    2024年02月10日
    浏览(8)
  • 【100天精通python】Day6:python基础_基本数据结构,常用序列类型和运算符

    目录 目录 1 常用的序列类型 1.1 字符串(String)  1.2 列表(List) 1.3 元组 (Tuple)

    2024年02月14日
    浏览(13)
  • springboot第49集:【思维导图】多线程,常用类与基础API,集合框架,泛型,数据结构源码...

    springboot第49集:【思维导图】多线程,常用类与基础API,集合框架,泛型,数据结构源码...

    多线程创建方式一:继承Thread类 多线程创建方式二:实现Runnable接口 jdk5.0新增两种创建多线程的方式 image.png image.png image.png image.png image.png image.png image.png image.png image.png image.png image.png image.png image.png 优先级 image.png image.png image.png image.png image.png image.png 线程安全问题 image.p

    2024年01月21日
    浏览(10)
  • 数据结构--》掌握数据结构中的排序算法

    数据结构--》掌握数据结构中的排序算法

            当我们面对海量数据时,如何高效地将其排序是数据结构领域中一个重要的问题。排序算法作为其中的关键部分,扮演着至关重要的角色。         无论你是初学者还是进阶者,本文将为你提供简单易懂、实用可行的知识点,帮助你更好地掌握排序算法在数据

    2024年02月08日
    浏览(11)
  • 数据结构--》掌握数据结构中的查找算法

    数据结构--》掌握数据结构中的查找算法

            当你需要从大量数据中查找某个元素时,查找算法就变得非常重要。         无论你是初学者还是进阶者,本文将为你提供简单易懂、实用可行的知识点,帮助你更好地掌握查找在数据结构和算法中的重要性,进而提升算法解题的能力。接下来让我们开启数据

    2024年02月08日
    浏览(9)
  • 数据结构 栈(一篇基本掌握)

    数据结构 栈(一篇基本掌握)

            时间就是生命,时间就是速度,时间就是气力。——郭沫若;本章继续学习数据结构,本章主要讲了什么是栈以及栈的基本功能和实现方法。    话不多说安全带系好,发车啦 (建议电脑观看) 。 附:红色,部分为重点部分;蓝颜色为需要记忆的部分(不是死记

    2024年02月12日
    浏览(10)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包