C語言資料結構線性表(順序表和單鏈表)
C語言線性表
C語言資料結構
線性表的順序儲存:
順序儲存的方法有陣列與動態分配記憶體空間(關鍵在於這兩者都可以在記憶體中分配一段連續的記憶體空間)
1.利用陣列來完成一個順序表:
首先定義一個結構體(如學生資訊):
typedef struct student
{
char stu_ID;//學生學號
char name[10];//學生姓名
int score;//學生成績
}student;//結構體資料型別名
然後再main函式裡面定義一個結構體陣列
int main()
{
student st[n];//n為你所需要的資料個數
return 0;
}
最後再自定義一些對這個結構體陣列進行操作的函式。
2.使用動態分配記憶體空間:(包含三者的標頭檔案stdilb.h)
在此之前你需要了解C語言中的幾個關於動態空間管理函式(malloc、realloc、free)
-
malloc(申請動態記憶體空間)
void* malloc(n*sizeof(資料型別));//申請成功後會返回這段連續空間的首地址,此時這個接收的指標就相當於一維陣列名
可以看到這個函式的返回值是一個void*的指標,它可以用來指向任何型別的資料空間,但是在使用時我們需要將它強制轉換為我們所需要的資料型別的指標(可以是基本資料型別:int float double char 也可以是構造資料型別結構體:那就需要注意自己構造的資料型別名)
-
realloc(改變動態空間大小)
void* realloc(n*sizeof(資料型別),(n+1)*sizeof(資料型別));
這個函式主要用於順序儲存,在順序儲存中我們一開始就分配了我們所需要的連續的記憶體空間,但是在後續的造作當中我們往往是需要新增資料或刪除資料的,這是就需要擴充或者減少記憶體空間的長度。
-
free(釋放動態空間)
void free(void *p);
這個函式的功能主要是釋放掉我們剛剛分配的記憶體空間,執行這個函式之後,系統就可以重新把這部分空間分配給其它變數或者程序。
-
補充申請動態記憶體函式還有(calloc)
void* calloc(n,sizeof(資料型別));//有兩個引數(資料個數,資料型別)
與函式malloc一樣,分配成功後也是返回這段連續空間的首地址,分配失敗後返回NULL。
接下來進行操作
首先定義兩個結構體
typedef struct student//用於存放資料
{
int stu_ID;//學生學號
char name[10];//學生姓名
int score;//學生成績
}student;//結構體資料型別名
typedef struct stu_Adm
{
student *pstu;//定義一個與資料型別同名的指標變數用於接收分配空間以及訪問結構體成員
int lenth;//用於記錄表長
}Adm;
接下來定義一個初始換函式
void init_stu(Adm &L,int n)//再main函式裡面定義一個結構體變數L Adm L;
{
L->pstu=(student*)malloc(n*sizeof(student));//分配一個連續空間給pstu
if(L->pstu==NULL)
{
printf("分配空間失敗!\n");
}
L->lenth=0;//當前資料個數
return;
}
然後再定義輸入函式、輸出函式等等。(從這裡開始一個長度為n,首地址即L->pstu的順序表就已經構造好了)
參考程式碼:
#include <stdio.h>
#include <stdlib.h>
#define MAX 10
int i;//定義一個全域性變數i用於後續for迴圈
typedef struct student//定義一個學生資訊結構體
{
char stu[11];//學號
char name[8];//姓名
int score;//成績
}student;
typedef struct Adm
{
student *ps;//用於間接訪問
int lenth;//用於計數
}Adm;
void init_stu(Adm *L,int n)//初始化順序表(Adm* 表示形式引數是這個型別的指標,"L"是一個地址值用於接收分配的地址) n表示分配這個資料型別的長度即資料個數
{
L->ps=(student *)malloc(n*sizeof(student));//呼叫標頭檔案函式malloc分配記憶體空間並將首地址返回給定義的結構體變數
if(L->ps==NULL)
{
printf("分配空間失敗!");
exit(-1);//結束當前程序 返回0是正常退出 不為0表示異常退出
}
L->lenth=0;//表示當前資料個數
return;
}
void Input(Adm *L,int n)//輸入函式
{
if(n<0||n>MAX)//n小於一或大於十
{
return;
}
for(i=0;i<n;i++)
{
scanf("%s %s %d",&L->ps[i].stu,&L->ps[i].name,&L->ps[i].score);
} //由於mallco函式動態配了一段連續空間的首地址給指標變數ps 現在ps就相當於一個一維陣列名(這裡就是資料結構型別(student)的結構體陣列名)
L->lenth=n;
return;
}
void Output(Adm *L,int n)
{
if(n<0||n>MAX)
{
return;
}
for(i=0;i<n;i++)
{
printf("%s %s %d\n",L->ps[i].stu,L->ps[i].name,L->ps[i].score);
}
}
void Insert(Adm *L,int num,int n)//插入函式
{
if(num<0||num>n+1||num>MAX)
{
printf("插入位置不合理!");
return;
}
L->ps=(student *)realloc(L->ps,(n+1)*sizeof(student));//realloc擴充分配的記憶體空間(這裡只新增一個所以n+1)
if(L->ps==NULL)
{
printf("擴充記憶體空間失敗!");
exit(-1);
}
for(i=n+1;i>num-1;i--)//擴充空間之後第n+1的位置沒有元素將n->n+1同理依次移動
{
L->ps[i]=L->ps[i-1];
}
printf("請輸入你需要增加的資料(學號 姓名 單科成績):\n");
scanf("%s %s %d",&L->ps[num-1].stu,&L->ps[num-1].name,&L->ps[num-1].score);
L->lenth=n+1;
return;
}
student Dele(Adm *L,int num1,int n)//刪除函式
{
student em;//定義一個空的同類型結構體用於刪除位置賦值為空
student mid;//定義一箇中間變數mid用於存放刪除的資料
if(num1<0||num1>n||num1>MAX)
{
printf("刪除位置不合理!\n");
return em;
}
mid=L->ps[num1-1];//先將被刪資料賦值給mid
L->ps[num1-1]=em;//再將被刪位置賦值為空
for(i=num1-1;i<n;i++)
{
L->ps[i]=L->ps[i+1];
}
L->lenth=n-1;//資料個數減一
return mid;
}
int main()
{
int n,num,num1;
student num2;//用於存放刪除函式
printf("請輸入你需要的資料個數(n):\n");
scanf("%d",&n);
Adm L;
init_stu(&L,n);//呼叫初始化函式初始化順序表
printf("請輸入對應的資料(學號 姓名 單科成績):\n");
Input(&L,n);//呼叫輸入函式
printf("輸出資料為:\n");
Output(&L,n);//呼叫輸出函式
printf("請輸入你需要插入資料的位置(num):\n");
scanf("%d",&num);
Insert(&L,num,n);
n=L.lenth;
printf("插入後的結果為:\n");
Output(&L,n);
printf("請輸入你需要刪除資料的位置(num1):\n");
scanf("%d",&num1);
num2=Dele(&L,num1,n);
printf("刪除的值為:%s %s %d\n",num2.stu,num2.name,num2.score);
n=L.lenth;
printf("刪除後結果為:\n");
Output(&L,n);
free(L.ps);//程式結束釋放由malloc分配的記憶體空間
return 0;
}
線性表的鏈式儲存:
鏈式儲存有別於順序儲存的地方就是空間在物理結構上並不連續了(即連結串列的每一個結點的空間不一定是相鄰的了)
顯然連結串列的空間分配不能再使用陣列而是需要動態分配記憶體空間了
不連續就意味著不能用陣列名加下標的方法來進行隨機存取了,那麼我們就需要利用指標來對每一個結點來進行連線
首先我們來看一看連結串列的結點的結構(仍以學生資訊為例):
typedef struct Lnode
{
資料型別 data;//資料域(這裡的資料型別可以是基本資料型別也可以是構造資料型別)
struct Lnode *next;//指標域
}Lnode,LinkList*;//定義結構體資料型別與其同類型的指標型別
這裡我們以帶頭結點的單鏈表為例:
1.資料域是用來存放我們所需要的資料,而指標域則是用於指向下一個結點
2.構造的資料型別Lnode主要是用於除了頭結點的結點進行指標或資料變數的定義,而LinkList*則是用於頭結點。
3.構造成功後Lnode* L等價於LinkList L。
知道結點的結構之後我們就可以開始建立連結串列了
建立一個單鏈表
typedef struct student//資料域結構體
{
char stu_ID;//學生學號
char name[10];//學生姓名
int score;//學生成績
}student;//資料域資料型別
typedef struct Lnode
{
student data;//結構體資料域變數
struct Lnode *next;//構造同類型指標用於指向結點本身
}Lnode,LinkList*;
LinkList init_list(LinkList L)//連結串列頭結點初始化
{
L=(LinkList)malloc(sizeof(Lnode));
if(L==NULL)
{
printf("分配記憶體空間失敗!\n");
exit(-1);
}
L->next=NULL;//此時只含頭結點
return L;//返回頭結點地址
}
void Trail(LinkList L,int Num)//尾插法 (需要在main函式中輸入所需資料個數Num和將初始化成功的連結串列頭結點的地址傳遞過來)
{
student Data;//定義一個同類型資料域用於迴圈輸入資料
Lnode *p,*q;//定義一個間接替換前一個結點指標的同類型指標p 同時定義一個尾插結點的指標
p=L;//先將頭結點的地址放在指標p中
while(i<Num)//迴圈結束條件
{
q=(Lnode*)malloc(sizeof(Lnode));//每一次增加一個結點都為其分配一個記憶體空間
if(q==NULL)
{
printf("分配記憶體空間失敗!\n");
return;
}
scanf("%s %s %d",&Data.num,&Data.name,&Data.score);//輸入中間轉換資料
q->data=Data;//將其賦值給每一次增加的結點的資料域
q->next=p->next; //開始操作 首先將前一個結點的指標域賦值給後一個結點
p->next=q;//然後將後一個結點的地址賦值給前一個結點
p=q;//最後讓這個新增的新節點又成為前一個結點然後進行迴圈
i++;
}
return;
}
此處忽略了main函式中的一些操作,主要是展示連結串列的構造。所謂”萬事開頭難“,能夠構造好連結串列之後,後續的操作也就水到渠成了
參考程式碼:
#include <stdio.h>
#include <stdlib.h>
int i=0,i1=1;//定義全域性變數用於後續的使用
typedef struct student
{
char num[8];
char name[8];
int score;
}student;//資料域
typedef struct Lnode
{
student data;//用於存放學生資訊
struct Lnode *next;//指標域
}Lnode,*LinkList;//定義結構體資料型別與指標資料型別
LinkList init_list(LinkList L)//連結串列的初始化
{
L=(LinkList)malloc(sizeof(Lnode));
if(L==NULL)
{
printf("分配記憶體空間失敗!\n");
exit(-1);
}
L->next=NULL;
return L;
}
void Trail(LinkList L,int Num)//尾插法
{
student Data;//定義一個同類型資料域用於迴圈輸入資料
Lnode *p,*q;//定義一個間接替換前一個結點指標的同類型指標p 同時定義一個尾插結點的指標
p=L;//先將頭結點的地址放在指標p中
while(i<Num)//迴圈結束條件
{
q=(Lnode*)malloc(sizeof(Lnode));//每一次增加一個結點都為其分配一個記憶體空間
if(q==NULL)
{
printf("分配記憶體空間失敗!\n");
return;
}
scanf("%s %s %d",&Data.num,&Data.name,&Data.score);//輸入中間轉換資料
q->data=Data;//將其賦值給每一次增加的結點的資料域
q->next=p->next; //開始操作 首先將前一個結點的指標域賦值給後一個結點
p->next=q;//然後將後一個結點的地址賦值給前一個結點
p=q;//最後讓這個新增的新節點又成為前一個結點然後進行迴圈
i++;
}
return;
}
void output(LinkList L)//定義一個連結串列的輸出函式用於輸出連結串列
{
Lnode *p;
p=L->next;
printf("學生資訊如下所示:\n");
while(p!=NULL)
{
printf("%s %s %d\n",p->data.num,p->data.name,p->data.score);
p=p->next;
}
return;
}
void Insert(LinkList L,int num1,int Num)//插入(這裡指標p的作用要理解) !!!p指標從頭指標開始指向每一個結點直到找到你所需要的結點為止
{
if(num1<0||num1>Num+1)//Num傳入函式用於此處的判斷越界
{
printf("插入的位置不合理!\n");
return;
}
Lnode *p,*s;//其中的s是用於輸入需要插入的結點(類似於頭插法只是不一定是在頭結點與首元結點之間插入)
p=L;
while(p!=0&&(i1<num1))//p還能指到元素(值不為空)
{
p=p->next;
i1++;
}
s=(Lnode *)malloc(sizeof(Lnode));
if(s==NULL)
{
printf("分配記憶體空間失敗!\n");
exit(-1);
}
printf("請輸入你需要插入的值(學號 成績 單科成績)\n");
scanf("%s %s %d",&s->data.num,&s->data.name,&s->data.score);
s->next=p->next;//p指到了要插入的前一個元素
p->next=s;
return;
}
LinkList Delete(LinkList L,int num2,int Num)//刪除
{
Lnode *em;
if(num2<0||num2>Num)
{
printf("刪除的位置不合理!\n");
return em;
}
Lnode *Det,*p;//用於存放刪除資料
p=L;
while(p!=0&&(i1<num2))//這樣結束迴圈後p指向了要刪除的前一個結點的位置
{
p=p->next;
i1++;
}
Det=p->next;
p->next=Det->next;
return Det;
}
int main()
{
int Num,num1,num2;
LinkList L,H,Det;
H=init_list(L);
printf("請輸入你需要的資料個數(Num):\n");
scanf("%d",&Num);
printf("請輸入對應資料(學號 姓名 單科成績):\n");
Trail(H,Num);
output(H);
printf("請輸入你需要插入的位置(num1):\n");
scanf("%d",&num1);
Insert(H,num1,Num);
output(H);
printf("請輸入你需要刪除的位置(num2):\n");
scanf("%d",&num2);
Det=Delete(H,num2,Num);
printf("刪除的值為:%s %s %d\n",Det->data.num,Det->data.name,Det->data.score);
output(H);
free(H);
return 0;
}
關於線性表的操作還有很多,此處只是提到了插入與刪除(參考程式碼僅供參考本人很菜)