这是橘子心酸的博客,致力于记录学习所得,分享学习内容,如存在部分问题,感谢友友指教。
最大的庸俗就是装腔作势,最大的媚俗就是人云亦云,最大的卑微就是顾影自怜。--王蒙
文章目录
一、什么是顺序表
顺序表是在计算机内存中以数组的形式保存的线性表,线性表的顺序存储是指用一组地址连续的存储单元依次存储线性表中的各个元素、使得线性表中在逻辑结构相邻的数据元素存储在相邻的物理存储单元中,即通过数据元素物理存储的相邻关系来反映数据元素之间逻辑上的相邻关系。
简言之,顺序表可以看做一个一维数组。
二、ADT(抽象数据模型)
定义线性表结构:
typedef struct Lnode{ //List为类型名
ElemType val;//数据对象
int i; //操作集,表示数据在表中的位置(数据关系)
}*List;
顺序表的主要操作方法:
1、List InitList(); //初始化一个空表
2、void ErgodicList(List L) //遍历线性表
3、void InsertValue(List L,int i,Elemtype val)//在第i个位置插入val
4、ElemType FindFromIndex(List L,int i);//依据下标查找值
5、int FirstValue(List L,Elemtype val);//查找第一个val出现的位置
6、void DelFromIndex(List L,int i);//删除i位置上的值
7、int getLen(List L);//返回线性表L的长度
三、顺序表存储结构的实现
1、数组连续储存空间实现
#include<iostream> //预处理
using namespace std;
#define INITSIZE 10
typedef int ElemType;
typedef struct Lnode { //定义数组类型的线性表
ElemType val[INITSIZE];
int last;
}*List;
List InitList() //初始化一个空表
{
List L=new Lnode;
L->last = -1;
return L;
}
void ErgodicList(List L)//遍历线性表
{
if (L->last == -1) {
cout << "线性表为空!" << endl;
return;
}
for (int i = 0; i <= L->last; i++)
cout << L->val[i] << " ";
}
void InsertValue(List L, int i, ElemType val)//在第i个位置插入val
{
if (L->last == INITSIZE - 1) {
cout << "顺序表表满!" << endl;
return;
}
if (i<0 || i>L->last + 1) {
cout<<"插入位置不合法!"<<endl;
return;
}
for (int j = L->last; j >= i; j--)
L->val[j + 1] = L->val[j];
L->val[i] = val;
L->last++;
}
ElemType FindFromIndex(List L, int i)//依据下标查找值
{
if (i<0 || i>L->last) return -1;
return L->val[i - 1];
}
int FirstValue(List L, ElemType val)//查找第一个val出现的位置
{
if (L->last == -1) {
cout << "线性表为空!" << endl;
return -1;
}
int i = 0;
while (i <= L->last && L->val[i] != val)
i++;
if (i > L->last) return -1;
return i+1;
}
void DelFromIndex(List L, int i)//删除i位置上的值
{
if (i<0 || i>L->last + 1) {
cout << "删除位置不合法!" << endl;
return;
}
for (int j = i; j <= L->last; j++)
L->val[j-1] = L->val[j];
L->last--;
}
int getLen(List L)//返回线性表L的长度
{
return L->last+1;
}
2、线性表链式储存结构实现
#include <iostream> //预处理
using namespace std;
typedef int ElemType;
typedef struct node{ //定义线性表结构
ElemType data;
struct node* next;
}Node,*LinkList;
Node* CreatFormTail(int n) /*尾插法创建链表*/
{
if(n < 1) return NULL;
Node *head, *p, *r;
r = head = new Node;
for (int i = 1; i <= n; i++)
{
p = new Node;
cout << "请输入第" << i << "个结点的值:";
cin >> p->data;
r->next = p;
r=p;
}
r->next = NULL;
return head;
}
void DisplayList(LinkList L) /*遍历链表*/
{
Node* head;
head = L->next;
if (head == NULL) {
cout << "链表为空" << endl;
return;
}
cout << "链表:";
while (head)
{
cout << head->data <<" ";
head = head->next;
}
cout << endl;
}
void InsertNode(LinkList L, int i,ElemType e) /*向链表中插入值*/
{
int j = 1;
Node* head = L->next, * preNode = L;
Node* insertNode = new Node;
while (head)
{
if (j == i) {
insertNode->data = e;
preNode->next = insertNode;
insertNode->next = head;
break;
}
j++;
preNode = preNode->next;
head = head->next;
}
}
ElemType FindFromIndex(Node L, int i)//依据下标查找值
{
i--;
LinkList node = L.next;
while (node&&i--)
{
node = node->next;
}
return node->data;
}
int GetAddress(Node L,ElemType e) /*获取链表中第一个e的地址*/
{
int i=0;
LinkList node = L.next;
while (node)
{
i++
if(node->data==e) return i;
node = node->next;
}
return NULL;
}
ElemType DelNodeFromLocate(LinkList L, int i) /*删除指定位置的结点*/
{
int j = 1;
Node* head = L->next, * preNode = L;
while (head)
{
if (j == i) {
ElemType e=head->data;
preNode->next = head->next;
break;
}
j++;
preNode = preNode->next;
head = head->next;
}
return e;
}
int GetLength(Node L) /*获取链表的长度*/
{
int i = 0;
LinkList node=L.next;
while (node)
{
i++;
node = node->next;
}
return i;
}
四、思路与算法分析
1、线性表初始化
数组:直接使用new运算符创建线性表结构,然后需要初始化其数据域数据,并且尾指针指向-1,即什么都不指向,因为里面没有值.
时间复杂度:O(1)
空间复杂度:O(1)
单链表:单链表创建时可以选择头插法和尾插法两种模式创建,即尾插法创建时,先创建一个新结点和临时结点,临时结点指向头结点,将值赋给结点的数据域,使临时指针域指向新结点,将临时结点指向新节点,如此循环即可创建。
时间复杂度:O(n)
空间复杂度:O(n)
2、线性表遍历
数组:将数据使用指针或者引用的方法,传入函数,判断尾指针指向,若依旧是-1,则不用遍历,返回线性表为空,若有值,使用循环语句遍历,截止条件为last指向的位置。
时间复杂度:O(n)
空间复杂度:O(n)
单链表:思路一致,但判断条件为结点是否为空结点。
3、向线性表插入值
数组:先判断线性表是否已满,若已满,可以申请更大的储存空间,将原有数据转移过去,进而通过循环结构将插入位置i后面的元素全部后移,留出空间,最后将值放在位置i上即可。
时间复杂度:O(n)
空间复杂度:O(n)
单链表:尾插法创建时,先创建一个新结点和临时结点,临时结点指向头结点,将值赋给结点的数据域,使临时指针域指向新结点,将临时结点指向新节点,如此循环即可创建。
时间复杂度:O(n)
空间复杂度:O(n)
4、依据下标查找值
这个比较简单,可以直接返回i位置的值。若是通过值查找其位置,则需要创建一个变量作为计数器(数组模式则不需要)记录元素位置,直到遍历到要查找的值截止。主要代码实现可为
while(i<=L.len&&val != ->data[i]){
i++;
}
if (i > L->last) return -1;//遍历完了依旧没找到元素
return i+1;//因为数据从0开始存放,位置则需要+1;
时间复杂度:O(1)
空间复杂度:O(n)
5、查找第一个val出现的位置
数组:见序号4分析
单链表:创建一个计数器,遍历链表,计数器+1,当链表的数据域的值=val时;返回计数器的值即可,
时间复杂度:O(n)
空间复杂度:O(n)
6、删除i位置上的值
数组:可以通过遍历i及i后面的元素,将i后面的元素依次前移,即可删除i位置上的值。
时间复杂度:O(n)
空间复杂度:O(n)
单链表:可以创建一个计数器j,使用双指针的方法,当i=j时,使该结点的前一节点指向,该结点的后一结点,则可删除i位置结点。
时间复杂度:O(n)
空间复杂度:O(n)
7、返回线性表的长度
数组:因为插入值的时候记录了线性表最后一个值所在的位置,故可以直接返回线性表的长度,将最后一个位置+1即可。
时间复杂度:O(1)
空间复杂度:O(1)
单链表:创建一个计数器,然后遍历结点,判断是否为空节点,若不为空结点,计数器+1,直到空结点,返回计数器的值即可。
时间复杂度:O(n)
空间复杂度:O(n)
五、总结
数组的优缺点:
数组是由一块连续的内存组成的数据结构。这个概念中有一个关键词“连续”,它反映了数组的一大特点,就是它必须是由一个连续的内存组成的。
数组的“连续”特征决定了它的访问速度很快,因为它是连续存储的,所以这就决定了它的存储位置就是固定的,因此它的访问速度就很快。比如现在有 10 个房间是按照年龄顺序入住的,当我们知道第一房子住的是 20 岁的人之后,那么我们就知道了第二个房子是 21 岁的人,第五个房子是 24 岁的人…等等。
祸兮福所倚,福兮祸所伏。数组的连续性既有优点又有缺点,优点上面已经说了,而缺点它对内存的要求比较高,必须要找到一块连续的内存才行。
数组的另一个缺点就是插入和删除的效率比较慢,假如我们在数组的非尾部插入或删除一个数据,那么就要移动之后的所有数据,这就会带来一定的性能开销.
单链表的优缺点:
链表是和数组互补的一种数据结构,它的定义如下:
链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针(Pointer)。由于不必须按顺序存储,链表在插入的时候可以达到 O(1) 的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要 O(n) 的时间,而顺序表相应的时间复杂度分别是 O(logn) 和 O(1)。
也就说链表是一个无需连续内存存储的数据结构,链表的元素有两个属性,一个是元素的值,另一个是指针,此指针标记了下一个元素的地址。