一、线性表
1.基本概念
2、基本运算
3、小结
二、顺序表(顺序存储)
1.定义:用顺序存储的方式实现线性表
后一个元素地址一定是原函数地址的 location 加上 size(ElementType)
2.顺序表的静态分配
如:int data[128]; //容易造成数据空间的浪费或者溢出。
3.顺序表的动态分配()/ malloc实战
利用指针来动态分配数组。如:
#include <iostream> //输出的头文件
#include <stdlib.h> //malloc和free函数的头文件
using namespace std;
#define InitSize 10//默认的顺序表最大长度
typedef struct {
int* data;//指示动态分配数组的指针
int MaxSize;//顺序表的最大容量
int length;//顺序表的当前长度
}SeqList;//封装这个叫SeqList的结构体
void InitList(SeqList& L)//初始化一个结构体
{
L.data = (int*)malloc(InitSize * sizeof(int));
//重新开辟一个初始化长度的内存给到L.data指针
L.length = 0;//当前顺序表长度为0(没有赋实值)
L.MaxSize = InitSize;//规定最大长度为默认的10
}
void IncreSize(SeqList& L, int len)
{
int* p = L.data;//把原来空间的首地址先赋给p,便于后面查找和释放
L.data = (int*)malloc((L.MaxSize + len) * sizeof(int));
//重新开辟一个加上len个int长度的内存,并强转换后给data
//【地址先挪过来,注意现在的L.data是在另一个空间里】
for (int i = 0; i < L.length; i++)
{
L.data[i] = p[i];
}//把原来顺序表的值挪过来
L.MaxSize = L.MaxSize + len;//现在顺序表的最大空间增加了len,但是新空间还未赋值
free(p);//释放原有的空间
}
int main()
{
SeqList L;
InitList(L);//建立一个长度为0的空链
cout << "Before:\n";
for (int i = 0; i < L.MaxSize; i++)
{
L.data[i] = i;//赋初值,填满顺序表
L.length++;//别忘记这个,否则不会执行IncreSize函数中挪值的操作
cout << L.data[i] << endl;//输出原有顺序表
}
IncreSize(L, 5);//换成另一个更长的内存点,存储原顺序表
cout << "After:\n";
for (int i = 0; i < L.MaxSize; i++)
{
cout << L.data[i] << endl;
}//打印现有的顺序表
return 0;
}
4.顺序表的特点
a.随机访问,即可以在O(1)时间内找到第i个元素。//顺序表---顺序存储,内存相邻
b.存储密度高,每个节点只存储数据元素(不像链式存储还要加上指针/地址)。
c.拓展容量不方便(即使采用动态分配,则拓展过程中的时间复杂度也很高)
d.插入、删除操作不方便,需要移动大量元素。
三、顺序表的插入与删除
1.插入
(1)用法示例
#include <iostream>
using namespace std;
#define MaxSize 10//默认的顺序表最大长度
typedef struct {
int data[MaxSize];
int length;
}SeqList;
bool ListInsert(SeqList& L, int i, int e)//在第i位插入e元素
{
if (i < 1 || i > L.length + 1)//判断插入的地址是否有效,只能从1到length+1的位置插
return false;
if (L.length >= MaxSize)//如果位序已经到了静态数组最大长度,那肯定也不能插了
return false;
for (int j = L.length; j >= i; j--)//从最后位置开始往后移
L.data[j] = L.data[j-1];
L.data[i-1] = e;//注意第3位,在数组里是data[2]
L.length++;
return 1;//return true 不能省略
}
int main()
{
SeqList L;
L.length = 0;//必须有个初值,不然会是一个之前的废弃值
for (int i = 0; i < 5; i++)
{
L.data[i] = i;
cout << L.data[i] << endl;
L.length++;
}
bool decide = ListInsert(L, 3, 3);
cout << decide << endl;
for(int i=0;i<L.length;i++)
cout << L.data[i] << endl;
return 0;
}
(2)时间复杂度
2.删除
(1)用法示例
#include <iostream>
using namespace std;
#define MaxSize 10
typedef struct {
int data[MaxSize];
int length;
}SeqList;
bool ListDelete(SeqList& L, int i, int& e)
/*--------在第i位删除的元素带出来赋值给e,注意这里必须用&e,否则无法带出第i位
(实际上第i位赋给了ListDelete函数的新变量e,而并不是主函数中定义的e,不带&的话,
系统会提示带出的值还是-1!)--------*/
{
if (i < 1 || i > L.length )//已有的第1位到第length位可以删除
return false;
e = L.data[i - 1];//将第i位取出来给e(注意下标为位序-1)
for (int j = i; j < L.length; j++)
/*-------从第i+1位到第length位都要往前移动,对应数组下标从i到length-1 ----------*/
L.data[j - 1] = L.data[j];
L.length--;
return 1;//return true 不能省略
}
int main()
{
SeqList L;
L.length = 0;//必须有个初值,不然会是一个之前的废弃值
cout << "Before:\n";
for (int i = 0; i < 5; i++)
{
L.data[i] = i;
cout << L.data[i] << endl;
L.length++;
}
int tmp=-1;//分配一块内存,用于存放取出来的第i位,按理说赋值与否不会影响结果
if (ListDelete(L, 4, tmp))
printf("The element has been deleted,and the value is %d\n", tmp);
else
cout << "Fail!\n";
cout << "After:\n";
for (int i = 0; i < L.length; i++)//输出第1位至第length位,对应数组0到length-1
cout << L.data[i] << endl;
return 0;
}
(2)时间复杂度
最好情况O(1),最坏情况O(n),平均情况O((n-1)/2),即O(n)。
3.按位查找
(1)用法示例:
省略顺序表封装和创建链条等操作
int GetElem(SeqList L,int i)
{
if (i < 1 || i > L.length )
return -10000;
return L.data[i-1];/返回第i位,数组下标为i-1
}
int main()
{
SeqList L;
InitList(L);//建立一个长度为0的空链
cout << "Before:\n";
for (int i = 0; i < L.MaxSize; i++)
{
L.data[i] = i;
L.length++;
cout << L.data[i] << endl;//输出原有顺序表
}
cout<<GetElem(L,3)<<endl;
return 0;
}
(2)时间复杂度:O(1)
4.按值查找
(1)用法示例:
int LocateElem(SeqList L,int e)
{
for(int i=0;i<L.length;i++)
if(L.data[i]==e)
return i+1;
return 0;//若没找到,返回0,表示fail
}//注意这里只能比较元素,而不能比较结构体
//(实际上比较结构体是否相同需要将其元素分量一一比较)
int main()
{
SeqList L;
InitList(L);//建立一个长度为0的空链
cout << "Before:\n";
for (int i = 0; i < L.MaxSize; i++)
{
L.data[i] = i;
L.length++;
cout << L.data[i] << endl;//输出原有顺序表
}
cout<<LocateElem(L,9)<<endl;
return 0;
}
(2)时间复杂度
最好情况O(1),最坏情况O(n),平均情况O((n-1)/2),即O(n)。
四、链表(链式存储)
1.单链表定义及与顺序表的区别
单链表的各个除了存放数据元素外,还要存储指向下一个结点的指针。(每个结点有一个指针域)
顺序表:优点:可随机存取,存储密度高
缺点:要求大片连续空间,改变容量不方便
单链表:优点:不要求大片连续空间,改变容量方便
缺点:不可随机存储,而是顺序存取,要耗费一定空间存放指针
2.代码实现
2.1 链表开头插入数据(头插法)【注意变量范围】
(1)将head作为全局变量
(所有函数之前提前声明 Node* head=NULL,空指针,还未加结点)
#include <iostream>
using namespace std;
typedef struct Node
{
int data;
Node* next;
}Node;
Node* head;
void Print(int x)
{
Node* temp = head;//只需要声明一个指向结点的指针,不用生成新结点
while(temp != NULL)//注意这里用的应该是while不是if
{
cout << temp->data << endl;
temp = temp->next;
}
}
void Insert(int x)
{
Node* temp = new Node;//插入需要新结点,所以需要先生成内存
temp->data = x;
temp->next = NULL;
temp->next = head;//让原来首结点的“地址”储存到temp->next,如果直接写temp=head,则可能让temp也“指向”首结点
head = temp;
}
int main()
{
head = NULL;
cout << "How many numbers do you want to store?" << endl;
int i;
cin >> i;
int x;
for (int cnt = 0; cnt < i; cnt++)
{
cout << "Please enter the number:\n";
cin >> x;
Insert(x);
Print(x);
}
return 0;
}
(2)将head作为主函数的局部变量
这样传入插入和打印程序的是head的复制品,即头结点的地址。(注意返回后Print函数需要return,主函数需要让返回值赋值给head)
#include <iostream> //输出的头文件
using namespace std;
#define InitSize 10//默认的顺序表最大长度
typedef struct Node
{
int data;//数值域
Node* next;//指针域(我的变量名叫next,变量类型为指向Node的指针,即Node*)
}Node, * LinkList;//使用Node便于对内部数据进行处理
Node* Insert(Node *NewHead ,int x)//输入一个同样指向首结点的新指针
{
Node* temp = new Node;//temp是指向新结点的指针(也就是新结点的地址)
temp->data = x;//也就是(*temp).data=x;
temp->next = NULL;
//if (head != NULL)//看是否为空链表,不是的话
// temp->next = head;//将头指针储存的地址(下一个结点的地址)赋给新结点的指针域
temp->next = NewHead;//插入头结点,直接将头指针储存的地址(下一个结点的地址)赋给新结点的指针域
NewHead = temp;//然后让head储存新结点的地址
return NewHead;//从这里也可以看出,返回的另一个“新head”的指针(指向的temp)
}
void Print(Node *NewHead,int x)//传入头指针的值,即头结点的地址
//类似于,你输入了一个新指针(存的首结点的地址),不过你指向的也是首结点。
{
while (NewHead != NULL)//头指针指向的地方不为空,首结点不为空,可以继续输出
{
cout<< NewHead->data << endl;
NewHead = NewHead->next;
}
}
int main()
{
Node* head= NULL;//先声明头指针指向空链表(注意这里是局部变量,函数调用只改变形参值)
cout << "How many Nodes do you want to store?" << endl;
int i;
cin >> i;
int x;
for (int cnt = 0; cnt < i; cnt++)
{
cout << "Enter the number:\n";
cin >> x;
head = Insert(head,x);
Print(head,x);
}
return 0;
}
(3)head为局部变量,但是采取引用符号传入实参或者直接插入头指针的地址
a.利用引用符号
void Insert(Node* &Head, int x)
...
void Print(Node* &Head, int x)
...
int main()
{
Insert(head,x);
Print(head,x);
}
b.利用头指针的地址改变插入函数
void Insert(Node** head, int x)
//此时head是头指针的地址,解引用会变成头指针的内容,即头结点的地址
{
Node* temp = new Node;//分配内存
temp->data = x;
temp->next = NULL;//定义新结点
if (*head != NULL)//这里*head表示对真正头指针进行解引用,*head就是头结点本身有没有
temp->next = *head;
*head =temp;
}
void Print(Node *NewHead,int x)//传入头指针的值,即头结点的地址
//类似于,你输入了一个新指针(存的首结点的地址),不过你指向的也是首结点。
{
while (NewHead != NULL)//头指针指向的地方不为空,首结点不为空,可以继续输出
{
cout<< NewHead->data << endl;
NewHead = NewHead->next;
}
}
int main()
{
Insert(&head,x);
Print(head,x);
}
注意:
1.Insert函数需要新结点,所以需要先定义新结点的x和temp。
Print函数:若函数定义时候传入的是head实参(全局定义或者head的引用、地址),则需要temp指针来帮助保证原链表的完整性。[只需要一个指针,无需生成新结点,故不需要定义新结点。]
2.区分temp和temp->next ,前一个是指向临时结点的指针,也是临时结点的地址
后面是临时结点储存的后一个结点(如果后面不为空的话)的地址。
3.用的是while循环而不是if,因为要多次打印。
2.2 任意次序插入(Insert at nth)
#include <iostream>
using namespace std;
typedef struct Node
{
int data;
Node* next;
}Node;
Node* head;
void Insert(int data,int n)//n 是你想要成为的次序
{
Node* temp1 = new Node;//插入需要新结点,所以需要先生成内存
temp1->data = data;
temp1->next = NULL;
if (n == 1)
{
temp1->next = head;//让原来首结点的“地址”储存到temp->next,如果直接写temp=head,则可能让temp也“指向”首结点
head = temp1;
return;
}
Node* temp2 = head;
for (int i = 0; i < n-2; i++)//循环走n-2次,如果要变化到第n位,则需要找到它的前一位(n-1),其数组下标n-2
//比如要插入到第三位,则需要找到第二个结点,
//而temp2指针这会指向的是第一位,所以需要移动1次(n=3,移动1次)
{
temp2 = temp2->next;//循环n-2次,让temp2指向插入位置的前一个
}
temp1->next = temp2->next;//后面的都归你
temp2->next = temp1;//你的地址存给我,以后你在我后面
}
void Print()
{
Node* temp = head;
while (temp != NULL)
{
cout << temp->data;
temp = temp->next;
}
}
int main()
{
head = NULL;
Insert(2, 1);//List=2
Insert(4, 2);//List=24
Insert(3, 1);//List=324
Print();
return 0;
}
图解:
这里主要分为两种情况:一是插入的位置有前结点,则需要temp2指向前结点。第二种是插入到第一个结点处,即成为头结点,需要特殊考虑。(变成前面的情况了)
2.3 任意次序删除
相比于插入,删除无需生成新结点,但是需要两个指针:temp1指向需要删除的结点,temp2指向其前一个结点。
#include <iostream>
using namespace std;
typedef struct Node
{
int data;
Node* next;
}Node;
Node* head;
void Insert(int data)//(尾插法)
{
Node* temp1 = new Node;
temp1->data = data;
temp1->next = NULL;
if(head == NULL)//没有头结点的情况下,直接插入
{
temp1->next = head;
head = temp1;
return;
}
Node* temp2 = head; //反之,在有尾结点时,需要用temp2找到尾结点
while (temp2->next != NULL)
temp2 = temp2->next;
temp2->next = temp1;//尾部插入
}
void Print()
{
Node* temp1 = head;//定义临时指针变量依次输出
while (temp1 != NULL)
{
cout << temp1->data << endl;
temp1 = temp1->next;
}
}
void Delete(int &n)
{
struct Node* temp1 = head;
if (n == 1)
{
head = head->next;
delete temp1;
return;
}
for (int i = 0; i < n - 2; i++)//将temp1指向删除目标的前一个
temp1 = temp1->next;
Node* temp2 = temp1->next;//将temp2指向需要删除的目标(注意该目标未被使用时需要delete)
temp1->next = temp2->next;//删除目标的后一个的地址,赋值给删除目标前一个的指针域
//即从前一个直接跳到后一个
delete temp2;
}
int main()
{
head = NULL;
Insert(2);//List=2
Insert(4);//List=24
Insert(3);//List=243
Print();
cout << "Please enter a position to delete:\n";
int x;
cin >> x;
Delete(x);
Print();
return 0;
}
2.4 反转一个链表
2.4.1 迭代法
#include <iostream>
using namespace std;
typedef struct Node
{
int data;
Node* next;
}Node;
Node* head;
void Insert(int data)//在尾部插入
{
Node* temp1 = new Node;
temp1->data = data;
temp1->next = NULL;
if (head == NULL)
{
temp1->next = head;
head = temp1;
return;
}
Node* temp2 = head; //反之,在有尾结点时,需要用temp2找到尾结点
while (temp2->next != NULL)
temp2 = temp2->next;
temp2->next = temp1;//尾部插入
}
void Reverse()
{
Node* current = head;
Node* pre = NULL;
Node* next;
//Node* next = current->next;不能在外面声明,因为下一次还需要让next移动
while(current!=NULL)
{
next = current->next;//让next移动到下一结点,记录下一结点的地址
current->next = pre;//让current指针域存放前一结点的地址
pre = current;//让pre指向当前地址
current = next;//current指向下一结点地址。
}
head = pre;//头指针指向原来的最后一个结点,完成迭代的反转!
}
void Print()
{
Node* temp1 = head;//定义临时指针变量依次输出
while (temp1 != NULL)
{
cout << temp1->data << endl;
temp1 = temp1->next;
}
}
int main()
{
head = NULL;
Insert(2);//List=2
Insert(4);//List=24
Insert(3);//List=243
Print();
Reverse();
Print();
return 0;
}
2.4.2 递归法
(1)逆序输出
#include <iostream>
using namespace std;
typedef struct Node
{
int data;
Node* next;
}Node;
Node* Insert(Node* head, int data)//在尾部插入
{
Node* temp1 = new Node;
temp1->data = data;
temp1->next = NULL;
if (head == NULL)//没有头结点的情况下,直接插入
{
temp1->next = head;
head = temp1;
return head;
}
Node* temp2 = head; //反之,在有头结点时,需要用temp2循环来找到尾结点
while (temp2->next != NULL)
temp2 = temp2->next;
temp2->next = temp1;//尾部插入
return head;
}
void Print(Node* p)
{
while (p != NULL)
{
cout << p->data << endl;
p = p->next;
}
}
//void RevPrint(Node* p)//逆向打印
{
if (p == NULL)
return;
RevPrint(p->next);//先递
cout << p->data;//后归
}
void Reverse(Node* p)//定义一个向后遍历的指针p
{
if (p->next == NULL)
{
head = p;
return;
}//前面反转完了,这是最终的退出条件
reverse(p->next);//如果第一个结点上式成立,则最后实现后面几句,以此类推
Node* q = p->next;//p指向倒数第二个结点,首先运行这几句
q->next = p;//q中存p的地址,实现反向;
p->next = NULL;//倒数第二个结点指向空。接下来轮到p指向倒数第三个结点的这几句
}
int main()
{
Node* head = NULL;
head = Insert(head, 2);//List=2
head = Insert(head, 4);//List=24
head = Insert(head, 3);//List=243
Print(head);
RevPrint(head);
return 0;
}
注意:使用局部变量时,需要改变的地方:
(1)主函数返回值(Node*)、主函数输入值(Node* head)
主函数需要return head
(2)调用插入函数时,需要返回:head=Insert(head,3),否则无法插入
(3)Print和RevPrint函数只需打印无需返回head
(2)链表反转
#include <iostream>
using namespace std;
typedef struct Node
{
int data;
Node* next;
}Node;
Node* head;
Node* Insert(Node* head, int data)//在尾部插入(需要返回头指针地址)
{
Node* temp1 = new Node;
temp1->data = data;
temp1->next = NULL;
if (head == NULL)//没有头结点的情况下,直接插入
{
temp1->next = head;
head = temp1;//这里如果不要temp1的话会造成资源的浪费
return head;
}
Node* temp2 = head; //反之,在有头结点时,需要用temp2循环来找到尾结点
while (temp2->next != NULL)
temp2 = temp2->next;
temp2->next = temp1;//尾部插入
return head;
}
void Reverse(Node* p)//这里不是把head带进去变,而是变了一个临时指针p!!
{
if (p->next == NULL)// exit condition,没有后结点,执行括号内
{
head = p;//head point at the last Node
return ;
}
Reverse(p->next);//有后结点时,递出去,同时执行后面三句
//这里注意,如果p如图所示为倒数第二个,则递出去的是最后一个
//下三句执行时,p指向的还是倒数第二个
Node* q = p->next;//p在前,q在后
q->next=p;//让p作为q的下一个
p->next = NULL;//而p指向空
}
void Print(Node* p)
{
while (p != NULL)
{
cout << p->data << endl;
p = p->next;
}
}
int main()
{
head = NULL;
head = Insert(head, 2);//List=2
head = Insert(head, 4);//List=24
head = Insert(head, 3);//List=243
Print(head);
Reverse(head);
Print(head);
return 0;
}
注意:这里head是全局变量,head=p这个条件应该出现在最内层(p->next==NULL),这样只是p和q在递归,不会改变head的位置。
递归部分同样可以写成下面的方式:
Node* Reverse(Node* head)
{
if (head->next == NULL)// exit condition
return head;
Node* last = Reverse(head->next);
head->next->next=head;//这里无法写成head.next.next
//可能是因为head只能被当作头指针(也没初始化结点的原因)
head->next = NULL;
return last;
}
注意:这种可以返回地址的函数,不用考虑全局变量还是局部变量,因为局部变量和全局变量可以同名,但是它们代表不同的变量。 当同名的变量在同一个作用域中存在时,局部变量将会隐藏全局变量。 在代码块或函数内部,使用该变量名时,优先使用的是局部变量。所以传入的相当于也是一个代号。
如果是反转前n个链表呢?
五、双链表
1.定义:
结点除了data和next指针,还有pre/prior指针
2.优缺点:
好处是更方便地找到前一个结点,在某些(如删除结点)操作起来,更方便
坏处是每个结点占用空间更大,且指针更多容易出错。
3.头插法:
(在头结点后面插)
#include <iostream>
using namespace std;
typedef struct Node
{
int data;
Node* next;
Node* pre;
}Node;
Node* head;
void InsertAtHead(int data)//在头部插入
{
Node* temp = new Node;
(*temp).data = data;
temp->next = NULL;
temp->pre = NULL;
if (head == NULL)
{
head = temp;
return; //base case不要忘记返回!
}
head->pre = temp;//我是你前者
temp->next = head;//你在我后面
head = temp;//我前面就是头指针
}
void ReversePrint(Node* p)//这里p就相当于声明除了一个临时变量temp
{
if (p->next == NULL)// 空链表直接退出
return;
while (p->next != NULL)//后面有结点,往后移动
p = p->next;//移动到最后尾结点
while (p != NULL)//从尾结点开始打印,指针往前移动
{
cout << p->data << endl;
p = p->pre;
}
}
void Print(Node* p)
{
while (p != NULL)
{
cout << p->data << endl;
p = p->next;
}
}
int main()
{
head = NULL;
InsertAtHead(2);//List=2
InsertAtHead(4);//List=42
InsertAtHead(3);//List=342
Print(head);
ReversePrint(head);
return 0;
}
4.后插法:
#pragma once
#include "iostream"
#include <cstring>
using namespace std;
typedef struct DNode
{
int data;
DNode* next;
DNode* pre;
}DNode;
DNode* head;
bool InsertNextDNode(DNode *p,DNode *s)//输入两个指向双链表结点的指针p和s
{
if (p == NULL || s == NULL)
return false;
s->next = p->next;//你后面的给我
if (p->next != NULL)//如果后续结点为空,就不用管下一个结点的pre指针了
p->next->pre = s;
p->next = s;//我在你后面
s->pre = p;//你在我前面
return true;
}
ps:单链表无法使用前插法,而双链表很容易实现,只需找到前驱结点,使用后插法即可。
5.双链表的删除
(删除p结点的后继结点)
bool DeleteNextDNode(DNode* p)
{
if (p == NULL)//本身结点不能为空吧
return false;
DNode* q = p->next;
if (q == NULL)//我作为你的后继结点,我不能为空吧
return false;
p->next = q->next;//无论q后面是NULL还是后面还有结点,都可以把q后面的地址存到p->next
if (q->next != NULL)//如果q后面有结点,才能将p存入后结点的pre中
q->next->pre = p;
free(q);
return true;
}
(在上述基础上,删除整个链表)
void DestroyList(DNode* head)
{
while (head->next != NULL)//只要头结点后面还有值,就一个一个删,知道只剩头结点
DeleteNextDNode(head);
free(head);//释放头结点空间,头指针还在
head= NULL;//让头指针指向空,防止野指针
}
总结:
(1)双链表不可随机存取,按位查找(在遍历的基础上添加一个计数器)、按值查找(数值对比一下)操作只能通过遍历的方式实现。时间复杂度O(n)。
(2)双链表需要特别注意操作结点是不是尾结点,如果是的话需要特殊处理。
六、循环链表
1.循环单链表
1.定义:尾结点指针L.next再指向NULL,而是指向头结点。(单结点的话,next指针指向自己)
2.作用:相比单链表,循环单链表可以从一个结点出发,找到其他任意一个结点。【循环遍历找到任意一个结点】
3.循环单链表:从头找到尾的时间复杂度O(n),从尾找到头的时间复杂度O(1).
ps:可以让一个指针L指向表尾结点【插入、删除时只需修改L即可】
2.循环双链表
1.定义:循环双链表的表头结点prior指向表尾结点;表尾结点的next指向头结点。
2.初始化循环双链表时,让头结点的prior和next结点都指向自己(区别于普通双链表前后指针均指向NULL)
3.双链表的插入和删除:直接缝补就行,不需要考虑操作结点是否为尾结点。
4.一些代码问题:
判空:只剩头结点(但不是数据结点),if(L->next==L)
表头(第一个数据结点):if(p->pre==head);
表尾(最后一个数据结点):if(p->next==head);
删除结点的答案见序号3.
七、静态链表(结构体数组)
1.定义
分配一整片连续的内存空间,各个结点集中安置,内存是连续的。顺序却是由下一个结点的数组下标(游标)决定【这里游标充当了指针的角色,不过游标不是地址,而是序号】。
【很像顺序表和链表的结合】
ps:如果游标为-1,则表示达到尾结点
2.如何用代码定义一个静态链表
#pragma once
#include <iostream>
using namespace std;
#define MaxSize 10
struct Node
{
int data;
int next=-2;//方便系统找到空结点
};
typedef struct
{
int data;
int next;//下一个在"结构体数组"的序号
}SLinkList[MaxSize];
void testSLinkList()
{
struct Node X;
cout << "sizeX=" << sizeof(X) << endl;
struct Node a[MaxSize];//结构体数组a
cout << "sizeA=" << sizeof(a) << endl;
SLinkList b;//静态链表,实际也是结构体数组
cout << "sizeB=" << sizeof(b) << endl;
}
int main()
{
testSLinkList();
return 0;
}
1.sizeof()是一个单目运算符,不需要头文件
2.注意cpp文件需要有main函数,不然会出现LNK错误,因为程序找不到入口。
总结:静态链表为数组实现的链表。
优点:增删操作不需要大量移动元素。
缺点:不能随机存取,只能从头结点依次往后查找,容量固定不变。
八、总结顺序表和链表
在呢故