几种线性表(C/C++)
一个小小的目录
- 几种线性表(C/C++)
- 基础知识
- 线性表基础
- C/C++ 结构struct
- C/C++指针
- 线性表
- 动态内存
- 代码
- 1.顺序线性表
- 2.逆序线性表
- 3.排序线性表
基础知识
线性表基础
C/C++ 结构struct
在C/C++语言中,我们可以自定义一个类型,所使用的函数就是struct。
如,统计学生的信息,比如有姓名,学号,排名等,我们就可以这样定义一个类型,然后对这个类型进行直接操作
其基本形式为:
struct name
{
int a;
char b;
int*p;
};
注意最后花括号也有个分号进行隔断。
当然,struct内部也可以用加上struct。
如何读取和使用struct定义类型内部的数据呢?
有两个方法:
1、"."法。
此方法就是用"."来引出定义类型内部的,某个类型值。
例子:
struct name
{
int a;
char b;
int*p;
};//定义结构name
int num = 0;//声明和初始化;
num = name.a;//取出name结构的int型 a的值;
2、"->"法
此方法需要用到指针,此指针需要是所定义结构类型的指针,然后进行取值。
例子:
struct name
{
int a;
char b;
int*p;
};
name*p;
int num = 0;//初始化
num = p->a;//赋值num 为 name结构的a的值;
C/C++指针
C语言的指针作用强大,涉及对内存地址的处理和使用,若加上动态内存,可以很好的迭代某个过程,如此处的线性表。
线性表
线性表则是很多个相同结构链接起来,一般可以是,一个线性表的节点内部有一个用于储存数据的变量,一个是指针,指向下一个节点,因此我们可以这样写:
struct node
{
int data;
node*next;
}//定义节点含有的数据
node node1;
node*p =&node1;
node node2;
cin>>p->data;
p->next = &node2;//赋值节点一内部的
很快我们可以发现,这样的话我们扩展时十分麻烦;
我们使用动态内存的方式进行迭代。
动态内存
对于C++中, 我们可以用 new 和 delete 来实现动态。
exa:
node *t = new node;
t->data = 2;
delete t;//每次用完则在分配的内存中删去t;应用于迭代,则可以实现一次一次的将内存动态分配。
代码
线性表虽然的节点是结构相似的,但是其扩展和添加方法是不一样的。主要是以下三种:
1.顺序线性表
线性表前面的顺序逆序排序均是由数据信息得来。
顺序线性表的意思就是按照输入数据的顺序进行线性表建立。
由于开头的指针指向第一个节点,因此我们需要在最后一个节点进行处理,对其内部储存的next指向下一个节点即可。
具体实现可以是:
1、获得头地址指向第一个节点;
2、得到第一个节点内部的next,并指向下一个节点。
3、循环
…
但是这样下去,我们会发现很麻烦,麻烦在重复确定最后一个节点,再在这个节点后面加新的节点。
于是我们可以每次从头地址,先用头地址定位到第一个节点,然后就用循环。
如何做到呢?
#include "linearList.h"
node *insertTail(node *h, node *t)
{
if(h==NULL)
{
h = t;
return t;
}
node *p =h;
while(p->next)
{
p = p->next;
}
t->next = NULL;
p->next = t;
return h;
}
每次都历遍到尾部,从尾部添加新的节点,成为顺序插入。
注:此头文件为:
#include <iostream>
using namespace std;
// 定义结点结构
struct node
{
int data; // 数据域
node * next; //指针域,指向下一个结点
};
// 函数insertTail:链表尾部插入
// 参数:h-链表头指针,t-指向要插入的结点
// 返回值:插入结点后链表的首结点地址
node *insertTail(node *h, node *t);
// 函数printList:输出链表,每个数据之间用一个空格隔开
// 参数:h-链表头指针
void printList(node *h);
// 函数delList:删除链表,释放空间
// 参数:h-链表头指针
void delList(node *h);
测试文件为:
#include "linearList.h"
int main()
{
int n,i;
node *t;
node *head=NULL; // 头指针为NULL,表示线性表为空,结点数为0
// 输入结点数
cin>>n;
for(i=0;i<n;i++)
{
// 为新节点动态分配空间
t = new node;
cin>>t->data; // 输入结点数据
t->next=NULL; // 结点指针域值为空
// 调用函数插入结点
head = insertTail(head, t);
}
// 输出链表
printList(head);
// 删除结点,释放空间
delList(head);
return 0;
}
// 函数delList:删除链表,释放空间
// 参数:h-链表头指针
void delList(node *h)
{
node *p=h; // 指针p指向头结点,第一个要删除的结点
while(p) // 这个结点是存在的
{
h = h->next; // 头指针h指向下一个结点(下一个结点的地址存在当前结点的指针域中,即h->next中
delete p; // 删除p指向的结点
p = h; // p指向当前的头结点,即下一个要删除的结点
}
}
// 函数printList:输出链表,每个数据之间用一个空格隔开
// 参数:h-链表头指针
void printList(node *h)
{
cout<<"List:";
while(h)
{// h为真,即h指向的结点存在,则输出该结点的数据
cout<<" "<<h->data; // 输出结点数据
h=h->next; // 将该结点的指针域赋值给h,h就指向了下一个结点
}
cout<<endl; // 输出换行符
}
2.逆序线性表
逆序线性表听起来貌似比顺序更加麻烦,想清楚后并不困难。
逆序只需要每次从head插入节点即可,注意的是,需要逆序的地方是需要将第一个输入节点(即最后一位输出的节点)的next赋值成为head地址,即他的下一个节点为head。并返回此节点到head,然后再添加的节点(倒数第二个),
其代码实现:
#include "linearList.h"
node * insertHead(node *h, node *t)
{
t->next = h;
return t;
}
测试文件
#include "linearList.h"
int main()
{
int n,i;
node *t;
node *head=NULL; //头指针为NULL,表示线性表为空,结点数为0
//输入结点数
cin>>n;
for(i=0;i<n;i++)
{
//为新节点动态分配空间
t = new node;
cin>>t->data; //输入结点数据
t->next=NULL; //结点指针域值为空
//调用函数插入结点到链表头部
head = insertHead(head, t);
}
//输出链表
printList(head);
//删除结点,释放空间
delList(head);
return 0;
}
//函数delList:删除链表,释放空间
//参数:h-链表头指针
void delList(node *h)
{
node *p=h; //指针p指向头结点,第一个要删除的结点
while(p) //这个结点是存在的
{
h = h->next; //头指针h指向下一个结点(下一个结点的地址存在当前结点的指针域中,即h->next中
delete p; //删除p指向的结点
p = h; //p指向当前的头结点,即下一个要删除的结点
}
}
//函数printList:输出链表,每个数据之间用一个空格隔开
//参数:h-链表头指针
void printList(node *h)
{
cout<<"List:";
while(h)
{//h为真,即h指向的结点存在,则输出该结点的数据
cout<<" "<<h->data; //输出结点数据
h=h->next; //将该结点的指针域赋值给h,h就指向了下一个结点
}
cout<<endl; //输出换行符
}
}
头文件同上顺序线性表
输出的第一个数是的最后一个输入节点,而每次输入的节点的next应指向前一个节点,因此将每个节点当head返回,下一个节点next指向head(即下一个节点),到最后,最后输入节点就当成head返回,printlist函数则从head开始输出每个数值,而最后输入的节点指向倒数第二个输入的节点,完成逆序线性表。
3.排序线性表
排序表可以算是最麻烦的,需要进行数值的比较再进行指针之间的指向。
参考头歌
利用两个指针p q,第一个指针p从head开始,第二个指针q从输入的节点开始,先进行比较,参考数值内的比较排序法,进行next的交换。
此处有三个指针,p,q,t,其中,p指针是用来确定插入节点的头,q指针用来确定插入指针的尾,t为待插入的指针。
首先来一个节点直接接入head,让q指针有值。此处相当于数值排序中max最大值的初始值(例如数组a[100]初始化的max = a[0])。
此处的思想是,判断排序法。利用每个q作为判断值,大于则插入并继续,若小于,则将p值换成q,q值向后寻,以找到大于新插入的数据大小。
判断方式:
三种情况:
第一种:开头,p指针之前没有节点了,只需处理从head处理,判断。
第二种:结尾,则是q运行到末尾,此处判断p。
第三种:在中间,则是利用p、q指针判断前后,前面已存在的节点指向新节点,新节点指向后已存在的节点,形成插入新的节点。可以想象在两根断绳中间利用打结加一根新的,则需首尾两个结。
再需注意的是,寻找新节点位置的方式。由于q指针比p指针先行动,利用q指针进行循环,中间插入判断插入的条件,再是到达尾部的情况和开头的情况即可。
#include "linearList.h"
node * insertSort(node *h, node *t)
{
if(h==NULL)
{
h =t;
return t;
}
node*p=NULL;
node*q = h;
while(q)
{
if(q->data > t->data)
{
if(p==NULL)//开头head
{
t->next = q;
return t;
}
else//在中间位置
{
p->next = t;
t->next =q;
return h;
}
}
else//如果if的判断条件不成立,则前推p和q,再次进行比较。
{
p =q;
q = q->next;
}
}
if(q==NULL)//到结尾,现插入的数据一定是最大值(由上述的循环可以得到),此时的p值是最后一个节点,则在p的节点连接新节点即可。
{
p->next=t;
t->next = NULL;
return h;
}//记得每个判断完成,即是找到相应情况进行返回。
}
注意这里的return,此处的循环时为了判断新输入的节点应在的线性表位置,而不是利用线性表一直往后推。每个新节点找到自己的对应位置以后即是插入成功了。此时返回头指针,以再次进行判断和插入数据。
因此对应的每种情况找到后进行插入,再即是返回h,再输入新的数据,利用动态地址创新的节点再进行下一次的插入判断和返回了。
测试文件为:
#include "linearList.h"
int main()
{
int n,i;
node *t;
node *head=NULL; //头指针为NULL,表示线性表为空,结点数为0
//输入结点数
cin>>n;
for(i=0;i<n;i++)
{
//为新节点动态分配空间
t = new node;
cin>>t->data; //输入结点数据
t->next=NULL; //结点指针域值为空
//调用函数插入结点,按data从小到大排序插入
head = insertSort(head, t);
}
//输出链表
printList(head);
//删除结点,释放空间
delList(head);
return 0;
}
//函数delList:删除链表,释放空间
//参数:h-链表头指针
void delList(node *h)
{
node *p=h; //指针p指向头结点,第一个要删除的结点
while(p) //这个结点是存在的
{
h = h->next; //头指针h指向下一个结点(下一个结点的地址存在当前结点的指针域中,即h->next中
delete p; //删除p指向的结点
p = h; //p指向当前的头结点,即下一个要删除的结点
}
}
//函数printList:输出链表,每个数据之间用一个空格隔开
//参数:h-链表头指针
void printList(node *h)
{
cout<<"List:";
while(h)
{//h为真,即h指向的结点存在,则输出该结点的数据
cout<<" "<<h->data; //输出结点数据
h=h->next; //将该结点的指针域赋值给h,h就指向了下一个结点
}
cout<<endl; //输出换行符
}
}