資料結構順序表和連結串列,以及LeetCode相關練習的習題
阿新 • • 發佈:2021-02-07
目錄
順序表
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<math.h>
/**************************************************
順序表: 邏輯結構和儲存結構都是順序的所以叫順序表
從索引位置插入,從索引位置彈出,並不用按順序來就跟給陣列賦值一樣
特點:一個指向堆中結構體的指標(堆中的結構體指向堆中的陣列,並且含有陣列的大小和已經儲存的資料數量)
在棧中就一個指標
最開始都是從下表為0的位置開始儲存的
作用:
**************************************************/
#define swap(a, b) \
{ __typeof(a) __temp = a; \
a = b; \
b = __temp; \
}
/***************結構體定義********************/
typedef struct shunxu_table //堆中的結構體用來指向堆中的陣列,
{
int *data; //指向堆中陣列首元素的指標
int size, length; //陣列的大小和已經儲存資料數量
}sx_table;
/***************資料結構操作定義********************/
sx_table* init_sx_table(int n) //在堆中開闢結構體記憶體,開闢陣列記憶體,需要引數傳入陣列大小
{
sx_table *p = (sx_table *)malloc(sizeof(sx_table)); //開闢結構體
p->data = (int *)malloc(sizeof(int) * n); //開闢陣列
p->size = n; //儲存陣列大小
p->length = 0; //初始化資料數量
return p;
}
int earse(sx_table *l,int ind) //刪除順序表l的陣列在ind處的資料,ind從0開始
{
if(l == NULL)
return 0;
if(ind < 0 || ind >= l->length) //當要擦除的資料在已有資料的邊界外時,擦不了,整個邊界包括等於號
return 0;
for (int i = ind; i < l->size; i++) //從前往後移動,將後一個數據賦值給前一個,填補空缺出來的位置
{
l->data[i] = l->data[i + 1];
}
l->length -= 1;
return 1;
}
void clear(sx_table *l) //先去掉陣列記憶體,再去掉結構體記憶體
{
free(l->data);
free(l);
return;
}
int expand(sx_table *l)
{
if(l == NULL)
return 0;
int extra_size = l->size;
int *p;
while (extra_size)
{
p = (int *)realloc(l->data, sizeof(int) * (extra_size + l->size));//在原有的記憶體位置擴充套件,所有不需要資料轉移
if(p) //擴充套件成功 ,更新結構體裡的變數
{
l->size += extra_size;
l->data = p;
return 1;
}
extra_size = extra_size >> 1;
}
return 0; //擴充套件失敗
}
int insert(sx_table *l,int ind,int val) //在l表的ind這個位置插入val大小的資料
{
if(l == NULL)
return 0;
if(ind < 0 || ind > l->length) //直接插入到已有資料的邊界外是不允許的
return 0;
if(l->length == l->size) //當已經儲存的資料數量等於陣列大小時,要擴充套件陣列大小
{
if(!expand(l)) return 0; //擴充套件失敗退出程式
}
for (int i = l->size; i > ind;i--) //從後開始把前一位的資料移到後一位,將被插入的那一位空出來
{
l->data[i] = l->data[i - 1];
}
l->data[ind] = val; //在空出來的那一位上插入資料
l->length += 1; //更新結構體儲存資料數量
return 1;
}
void out(sx_table *l)
{
if(l == NULL)
return;
printf("shuxubiao = [");
for (int i = 0; i < l->length; i++)
{
i && printf(", "); //0位不列印逗號
printf("%d ",l->data[i]); //以陣列的方式遍歷整個順序表
}
printf("]\n");
return;
}
int main(int argc, char const *argv[])
{
srand(time(0));
#define max_op 20
sx_table *vec = init_sx_table(max_op);
for (int i = 0; i < max_op; i++)
{
int op = rand() % 4;
int ind = rand() % (vec->length + 3) - 1;
int val = rand() % 100;
switch (op)
{
case 1:
case 2:
case 0:
{
printf("insert %d at %d to Vector = %d\n", val, ind, insert(vec, ind, val));
}
break;
case 3:
{
printf("erase a iterm at %d from Vector = %d\n", ind, earse(vec, ind));
}
break;
default:
break;
}
out(vec);
printf("\n");
}
clear(vec);
#undef max_op
return 0;
}
連結串列
/************************************
連結串列:從索引位置插入,從索引位置刪除
儲存的資料可以翻轉
************************************/
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
typedef struct listnode
{
int data;
struct listnode *next;
}ListNode;
typedef struct list
{
ListNode head; /*虛頭節點,方便後面55,56行,在往連結串列零位插入資料時方便,也方便指標移動到要插入節點的前一節點
(這裡的移動到是指指標指向要插入節點的前一節點) */
int size; //連結串列中有多少個節點,不包含上面的虛頭節點
}List;
ListNode *GetNewListNode(int val);
List *GetNewList();
void ClearNode(ListNode *);
void ClearList(List *);
int InsertNode(List *, int, int);
int Erase(List*,int);
void OutPut(List *);
void Reverse(List *);
int main(int argc, char const *argv[])
{
srand(time(0));
#define max_op 20
List *vec = GetNewList();
for (int i = 0; i < max_op; i++)
{
int op = rand() % 4;
int ind = rand() % (vec->size + 3) - 1;
int val = rand() % 100;
switch (op)
{
case 1:
case 2:
case 0:
{
printf("insert %d at %d to Vector = %d\n", val, ind, InsertNode(vec, val, ind));
}
break;
case 3:
{
printf("erase a iterm at %d from Vector = %d\n", ind, Erase(vec, ind));
}
break;
default:
break;
}
OutPut(vec);
printf("\n");
}
ClearList(vec);
#undef max_op
return 0;
}
/*
在下方往連結串列中插入或則擦除某個節點的函式中,一般要用到兩個指標來操作,一個指標找到要插入或擦除節點的前一節點,
另一指標主要是輔助抓住後面的連結串列,防止後面的斷掉
*/
ListNode *GetNewListNode(int val) //建立新的節點,主要是在插入節點函式中呼叫
{
ListNode *P = (ListNode *)malloc(sizeof(ListNode));
P->data = val;
P->next = NULL; //預設沒有下一個節點
return P;
}
List *GetNewList() //建立新的連結串列 ,程式開始呼叫
{
List *p = (List *)malloc(sizeof(List));
p->head.next = NULL; //預設沒有下一個節點
p->size = 0; //預設連結串列長度為0
return p;
}
int InsertNode(List *l, int val, int ind) //在連結串列l的ind位置插入一個數據為val的節點,ind從0開始
{
if(l==NULL) //判斷連結串列是否建立
return 0;
if(ind<0 || ind > (l->size)) /*判斷插入位置是否超出限制,這裡大於而不是大於等於因為剛開始插入時,連結串列本來就沒有節點,所以需要先建立一個節點,
後面要再插入位置就只能往連結串列內部插入,或者連結串列最後,也就是說連結串列大小隻能是往後一個節點的增加,
不能一下插入很遠的一個位置,不然就會導致中間的有空置 */
return 0;
//printf("Insert new node val:%d at %d", val,ind);
/*下面的整個過程就像給風箏的線中間再接一節線的過程:先是需要一隻抓住節點位置的手(這裡就是p,起到找到位置防止風箏飛走的作用),
一個要接入的新線(這裡就是新的節點node),然後手放在節點前一個位置while的作用,然後再將手放在節點的為位置並將新線node連線到此位置上,
然後將節點前的位置連線到新線並且斷開了與節點位置,新線成為新的節點*/
ListNode *p = &(l->head), *node = GetNewListNode(val);//定義兩個節點指標對它們好進行操作,第一個輔助節點指標指向虛頭節點,第二個指向最新建立的節點
while(ind--) //將p從指向head轉到指向要插入節點的前一個節點
p = p->next;
node->next = p->next; //將本來在插入節點的地址賦給新的節點的下一節點指標
p->next = node; //將新的節點的地址給上一節點的下一節點指標
l->size += 1; //將連結串列的節點數加1
return 1;
}
int Erase(List *l, int ind)
{
if(l == NULL)
return 0;
if(ind<0 || ind >= l->size)
return 0;
ListNode *p = &(l->head), *q;//這裡定兩隻手一隻是連線後段的,一隻是連線要刪掉的
while(ind--)
p = p->next;
q = p->next;
p->next = q->next; //這裡直接用p->next = p->next->next,可以讓後面一段直接連線到前面,但是中間那個節點會記憶體洩露
free(q);
l->size -= 1;
return 1;
}
void ClearNode(ListNode *node) //直接釋放掉當前節點的記憶體,主要是釋放連結串列時呼叫
{
if(node == NULL)
return;
free(node);
return;
}
void OutPut(List *l) //將連結串列儲存的資料列印輸出
{
if(l == NULL)
return;
ListNode *p = l->head.next;
printf("List[%d]=[",l->size);
for (int i = 0; i < l->size; i++) //這裡用的是while迴圈和指標不斷的訪問
{
printf("%d->", p->data);
p = p->next;
}
printf("NULL]\n");
// for (ListNode *p = l->head.next;p; p = p->next) //這一種程式碼最簡潔
// {
// printf("%d", p->data);
// }
}
void Reverse(List *l) //將連結串列內的儲存的資料順序完全顛倒過來
{
if(l == NULL)
return;
ListNode *p = l->head.next, *q;
/*
為什麼這裡的p不是指向要插入節點的前一個節點?(可以和上面的相比較一下)
因為這裡是頭插,插入的位置是固定的,那麼就可以用l->head.next表示一直變化的虛頭節點後一個節點的地址,就不需要再定義新的變數
*/
l->head.next = NULL;//這裡是斷開後重新開始插入
while (p)
{
q = p->next; /*先將P指向的下一個儲存給臨時q,避免下面p指向發生變化丟掉,這裡的p與上面插入函式的node是一個意義,都代表要插入節點的記憶體地址,
只不過一個是新申請的記憶體一個是打亂再插入,上面函式的p就是l移到某個位置所以和這裡的l是一個意思被插入連結串列中已經存在的地址
*/
p->next = l->head.next; /*將斷開後的頭節點下一節點地址賦值給重新插入的節點的指向下一個節點的指標
(說人話就是將斷開頭節點後面的一個節點連線到新查人的節點後面),這裡p原來的後面的連結串列就丟掉了 */
l->head.next = p;//將新插入的節點連線到頭節點後面
p = q;
}
return;
}
void ClearList(List *l)
{
if(l == NULL) //如果這裡為空就直接返回
return;
ListNode *p = &(l->head),*q; //將虛頭節點地址給p
p = p->next; //將下一個地址給p
free(l); //先將虛頭節點釋放記憶體
while(p) //通過判斷地址指標是否為0來判斷是否釋放完全
{
q = p->next; //q一個零時變數,因為要釋放掉當前指標指向的記憶體,所以得先把下一個記憶體的地址賦給零時變數,後面的記憶體才不會丟失
ClearNode(p);
p = q; //將下一個地址再賦給p一遍迴圈
}
return;
}