结构的嵌套
#include <iostream>
#include <cstring>
using namespace std;
struct Education
{
char major[30];
char degree[20];
double GPA;
};
struct Student
{
Education school;//结构中嵌套一个Education结构
char id[20];
int grade;
};
int main()
{
Student ss = { {"计算机科学与技术","本科",4.0},"123456789",3 };
cout << "专业:" << ss.school.major << "\n"
<< "学历:" << ss.school.degree << "\n"
<< "平均学分绩点:" << ss.school.GPA << "\n"
<< "学号:" << ss.id << "\n"
<< "年级:" << ss.grade;
return 0;
}
在c/c++是允许结构的嵌套的,而嵌套的结构数据成员的访问方式是:
<对象名>.<嵌套的结构变量名称>.<对应的数据成员名称>
例如,上述代码的中,Student结构嵌套了Education结构,在主函数定义了结构变量ss,ss.school.name就访问到了嵌套的结构变量。
注意:结构的嵌套里,结构成员不能是自身的结构变量,但可以是自身结构指针作为成员
链表结构
struct List
{
int data;
List *next;
};
上述结构就是一个简单的链表结构,它是通过结构指针将下一个结构变量连接起来,所以称之为链表,List *next就是一个钩子,钩住下一个结构。
使用链表结构的好处
链表结构可以方便数据的插入和删除,如果使用常规的数组,对于删除某一个元素会特别麻烦,因为:数组的内存结构是连续的,而链表结构则是随机分布的,所以对于数据的插入和删除来说,特别方便。
创建与遍历链表结构
#include <iostream>
using namespace std;
struct Node
{
int data;
Node *next;
};
Node *CreateListNode(int n)//无头结点创建链表
{
Node *pS;
Node *pEnd;
Node *Head;
pS=new Node;
cin>>pS->data;
Head=NULL;//一开始链表为空
pEnd=pS;
for(int i=0;i<n-1;i++)
{
if(Head==NULL)
Head=pS;
else
pEnd->next=pS;
pEnd=pS;//pEnd指针始终指向链表的尾部
pS=new Node;
cin>>pS->data;
}
pS=NULL;
pEnd->next=NULL;
return Head;
}
void showList(Node* Head)//链表的输出
{
Node* p=Head;
while(p)
{
cout<<p->data<<" ";
p=p->next;
}
cout<<endl;
}
int main()
{
Node *head=CreateListNode(5);
showList(head);
return 0;
}
上述代码中,CreateListNode函数创建链表是最常见的方法,这种链表创建是没有带头结点的。除此之外,链表的常用创建方法有头插法和尾插法创建。无论是哪一种创建方法,链表尾部的 next指针都是指向空指针NULL
遍历链表只需要一个头指针head即可,通过不断地移位操作
p=p->next即可让指针遍历整个链表,参照上述代码的showlist函数
尾插法创建链表的方法
Node *InitList()
{
Node* Head=new Node;
Head->NULL;
return Head;
}
Node *CreateListNodeInTail(Node *Head,int n)
{
Node *p=Head;
Node *temp=NULL;//初始化指针
for(int i=0;i<n;i++)
{
temp=new Node;
cin>>temp.data;
p->next=temp;
p=p->next;
}
p->next=NULL;
return Head;
}
上述代码实现了尾插法创建链表,这种链表创建方法的头结点没有用处,其优点在于:在删除链表元素的时候,不需要考虑删除的是否是头结点,如果是不带头结点的创建链表方式,这种链表的头结点也是头指针Head,那么在删除的时候就要特殊考虑,因为删除了这个结点,Head指针就和链表脱钩了。
删除链表结点
void DeleteData(Node *Head,int element)
{
Node* p=Head;
if(!Head)
{
cout<<"List is NULL"<<endl;
return;
}
if(p->data==element)
{
Head=Head->next;
delete p;
cout<<element<<" the head of List have been deleted"<<endl;
return;
}
for(Node *pGuard=Head;pGuard->next;pGuard=pGuard->next)
{
if(pGuard->next->data==element)
{
p=pGuard->next;//待删
pGuard->next==p->next;
delete p;
cout<<element<<" have been deleted"<<endl;
return;
}
}
cout<<element<<"is not found!"<<endl;
}
上述代码是实现链表删除某个特定元素的方法,链表是不含头结点的,下面我们看一下带头结点的删除特定元素的方法
void Delete(Node *Head,int element)
{
Node *p=Head;
while(p->next)
{
if(p->next->data==element)
{
Node *temp=p->next;
p->next=temp->next;
delete temp;
cout<<element<<" have been deleted"<<endl;
return;
}
p=p->next;
}
cout<<element<<" is not found"<<endl;
}
上述代码中,Delete函数是没有考虑头结点的,通过两种是否带有链表头结点删除操作是不相同的,对于不带头结点的链表,要考虑删除的结点是否为头结点,如果是,我们要将头指针移位,即Head=Head->next,然后把头结点删掉。
void delete_index(Node* head,int index)//删除下标为index的结点
{
Node* p=head->next;
Node* temp;
int flag=0;
int count=0;
while(p)
{
count++;
if(count==index-1)//让p指向要删除的位置的前一个位置
{
flag=1;
temp=p->next;
p->next=temp->next;
delete temp;//释放内存
}
}
if(!flag)
cout<<"error"<<endl;
}
上述代码实现的是删除下标为index的元素(含头结点)
插入链表结点
void Insert_index(Node* head,int e,int index)//在index位置插入元素e
{
Node* p=head->next;
Node* temp;
int flag;
int count=0;
while(p)
{
count++;
if(count==index-1)
{
temp=new Node;
temp->data=e;
temp->next=p->next;
p->next=temp;
flag=1;
}
}
if(!flag)
cout<<"Successful"<<endl;
else
cout<<"Failed"<<endl;
}
上述代码实现了在index位置插入一个结点
排序
void Sort_List(Node* head,int n)//冒泡排序法
{
Node* p=head->next;
Node* q=head;
for(int i=0;i<n;i++)
{
p=q->next;//重置表头指针
for(int j=0;j<n-i-1;j++)
{
if(p->data > p->next->data)
{
int temp=p->data;
p->data=p->next->data;
p->next->data=temp;
}
p=p->next;
}
}
showList(head);
}
根据结构体某一特定成员的值的排序,我们可以使用最简单的冒泡排序,上述代码中,利用指针q记录了表头指针,每次外层循环,指针p都要重置。至于为什么都是p=q->next,因为这个链表含头结点。
查找元素
void Search_index(Node* head,int index)//查找index 位置的元素
{
int count=0;
Node* p=head->next;
if(index<=0)
{
cout<<index<<" error"<<endl;
return;
}
while(p){
count++;
if(count==index)
break;
p=p->next;
}
if(p!=NULL)
cout<<p->data<<endl;
else
cout<<index<<" error"<<endl;
}
void Search(Node* head,int e)//查找链表是否存在数据e
{
Node* p=head->next;
int index=0;
while(p)
{
index++;
if(p->data==e)
break;
p=p->next;
}
if(!p)
cout<<index<<" exsits"<<endl;
else
cout<<"error"<<endl;
}
上述代码分别实现了链表查找下标为index位置的元素和链表是否存在元素element,比较简单。
*注意:以上的链表均是单向链表,链表还有双向的,以及循环链表,实际的实现方式也相差无几,双向链表中,除了next指针,还需要ex指针,这两个指针分别指向的是后一个结点和前一个结点。而循环链表只是原本单向的链表的末尾连接到了它的链首。 *
struct Node
{
int data;
Node* ex;
Node* next;
};
总之,最常见的是单向链表,但是其它两种链表各有优点,对于双向链表,在一些删除链表相同元素的题目中,我们可能要从链表的第二项开始,然后查找是否与前面的结点的对应的值相等,而对于具有ex指针的双向链表来说很简单,直接在里面套一层循环即可;对于循环链表,这种解决的问题比较特殊,可以比较方便地解决 约瑟夫环 这种问题,
循环链表的设置最好不要带头结点。而对于单向链表,设置了头结点对于删除结点是很好的,因为它避免了讨论删除的结点是否位于表头。