記事ディレクトリ
提案を引き出す
複数の生徒の情報を読み取り、処理します。生徒数が不明なため、生徒情報の保存には動的に割り当てられたメモリが使用されます。
struct student{ /*学生信息结构类型*/
char nu[7]; /*学号*/
char name[9]; /*姓名*/
} ;
main()
{
char nu[7];
struct student * ptr;
printf("请输入学生学号");
gets(nu);//读入学号
while(strcmp(nu,"0000")!=0){//判断学号是否合法
ptr = malloc(sizeof(struct student)) ;
strcpy(ptr->no,no);//学号复制到内存中
printf("请输入学生姓名");
gets(ptr->name); //读取姓名到内存中
printf("请输入学生学号");
gets(nu);
}
...
}
関係する関数:
gets()
、strcmp()
、strcpy
。
既存の問題点:生徒情報を格納するメモリは動的に割り当てられるため、各生徒情報を格納するメモリは通常不連続であるが、どのようにすれば生徒情報にアクセスできるのか?
解決策 1: 学生情報の格納アドレスをポインタ配列に保存します。
問題があります:ポインター配列の長さがまだ固定されています。
断片をチェーンに装着し、物理的に不連続な断片を論理的に連続した断片に変える
• ストレージのフラグメントをチェーンするにはどうすればよいですか?
•ポインタの使用- ポインタの本質はアドレスを格納することです。
•ストレージ割り当ての最小単位として構造を利用する
• 構造体にポインタを格納するコンポーネント(メンバ)を定義する
•動的ストレージ割り当てメカニズムを利用して構造メモリを割り当てます。
自己参照構造
type の変数をstruct book
構造体内で再定義することはできません。struct book
struct book
ただし、型へのポインターを定義することは可能です(自己参照構造struct book*
として知られています)。
自己参照構造体には、それ自体と同じ型の構造体へのポインター メンバーが含まれます。好き:
struct node{
int data;
struct node* pNext;
}
その中にはstruct node*
自己言及構造があります。
pNext
リンク (リンク) と呼ばれ、struct node
ある型の構造体変数を同じ型の別の構造体変数に接続するために使用されます。
struct node n1,n2;
n1.pNext=&n2;
リンクされたリスト
リンクリスト構造
学生情報:
typedef struct student{
//数据域
char num[7];//学号
char name[9];//姓名
//指针域
struct student* pNext;//记录下一个
}
3 人の学生の情報を保存する 3 つの構造変数を動的に適用します。3 つの構造変数は、先行者と後継者の関係でポインタを介して一緒に「連鎖」します。最初の構造体変数のアドレスはポインタに別途記録されます。
リンクされたリスト
-
意味:
これは、リンク ポインタによって連鎖された **自己参照構造変数 (ノードと呼ばれる)** の線形コレクションであり、線形テーブルの記憶構造です。
-
リンクリスト構造
- ノード: 3 つ以上の分岐を結合する回路内の点
- ノット: 直線または曲線の端点または交点
- ヘッド ノード:
pHead
- ヘッド ノードを指すポインタ変数 - 各ノット (ノード) ポイントは 2 つのフィールドで構成されます。
- データ フィールド- ノード自体の情報を保存します。
- ポインタ フィールド- 後続のノードへのポインタを格納します (単一リンク リストの場合)。
- 末尾ノードのポインタ フィールドは、リンク リストの終わりの印として NULL (バックスラッシュで表される) に設定されます。
3. リンクリストの特徴:
- リンク リストは、線形リストを格納するために使用されるストレージ構造です。
- リンク リストのノード (ノード) は、必要に応じて動的メモリ割り当て関数を呼び出すことによって割り当てられるため、リンクリストを必要に応じて拡張したり短縮したりすることができ、保存するデータの数が不明な場合でもメモリを節約できます。
- リンクリストのノードは論理的には連続していますが、通常、各ノードのメモリは不連続であるため、すぐにアクセスすることはできず、先頭ノードから順にノード単位でしかアクセスできません。
リンクリストの作成、ノードの挿入、削除
1. リンクリストの作成
最初からノードを 1 つずつ作成して結合 (リンク) し、次の手順を実行します。
- ヘッダーポインターを宣言する
- ノード(構造体)を生成する
- リンク (ヘッダーを挿入、または最後に追加)
- ぐるぐる回る
ノードの動的メモリ割り当て:
pNew = malloc(sizeof(struct node));
pNew->data = 17;
リンク リスト ノードの動的割り当てにより、メモリ内のノードの位置が必ずしも連続的ではないことがわかります。
typedef struct ListNode {
//ListNode:链表节点
int data; //定义data变量值,存储节点值
struct ListNode *next; //定义next指针,指向下一个节点,维持节点连接
} LISTNODE;
LISTNODE* creat_FIFO_List(){
//FIFO( First Input First Output)先进先出
//step1:变量定义;
//step2:变量初始化;
//stept3:创建节点
scanf("%d",&num);
while(num != -1){
pNew = malloc(sizeof(LISTNODE));
if(pNew != NULL){
pNew->data = num;
//step4:细化:将新节点插入链表
}
scanf("%d",&num);
}
return pHead;
}
最初のノードが作成されると、それは次pHead
のように指定されます。
それ以外の場合、新しく追加されたノードはリンクされたリストの末尾ノードに追加されます。
新しいノードをリンク リストの末尾ノードに簡単に追加できるようにするには、pTrail
リンク リストの末尾ノードを指すようにポインタを設計する必要があります。
追加プロセス:
pTrail->pNext = pNew;
pTtrail = pNew;
完全版:
LISTNODE* creat_FIFO_List(){
//step1+step2:定义变量+初始化
int num;
LISTNODE* pHead, pTrail, pNew;
pHead = NULL;
pTrail = NULL;
pNew = NULL;
printf("Input positive numbers, -1 to end.\n");
scanf("%d",&num);
while(num != -1){
//step3:创建节点,分配内存
//不管是pHead,pTrail,pNew,都是一个厂子出来的
pNew = malloc(sizeof(LISTNODE));
//step4:新节点插入链表
//得先判断内存申请成没成功
if(pNew != NULL){
pNew->data = num;
if(pHead == NULL){
//pHead从头到尾就只是占了个名字
pHead = pNew;
pTrail = pNew;
}else
{
pTrail->pNext = pNew;//追加
pTrail = pNew;
}
}
scanf("%d",&num);
}
pTrail->pNext = NULL;//设置链表结束标志
return pHead;
}
2. ノードアクセス
• リンク リスト内のノードへのアクセス方法: リンク リスト内のノードのメモリは動的に割り当てられるため、名前ではアクセスできず、ノードのアドレスによってのみアクセスできます。
•ノードのアドレスは、その先行ノードのアドレスフィールドに記録されているため、n 番目のノードにアクセスしたい場合は、まず n-1 番目のノードにアクセスして、ノードのアドレスフィールドを読み取る必要があります。 n-1 番目のノードにアクセスする場合は、まず n-2 番目のノードにアクセスする必要があり、最初のノードにアクセスするまで同様に続きます。そして最初のノードはheadPtr
ポインタで指されているので、最初のノードにアクセスできるので、2番目のノード、3番目のノードにもアクセスできます...
void print_List(LISTNODE* pNew){
if(pNew = NULL)
printf("The list is empty\n");
else{
printf("The list is:\n");
while(pNew != NULL){
printf("%d-->",pNew->data);
pNew = pNew->pNext;
}
printf("NULL\n\n");
}
}
メイン機能:
int main ()
{
LISTNODE* pHead;
pHead = creat_FIFO_List();
}
3. リンクリストノードの動的な追加と削除
増加
if(pPre == NULL){
//插在链表首结点
pNew->pNext = pCurrent;
pHead = pNew;
}
else if(pCurrent = NULL)
{
//插在结尾
pPre->pNext = pNew;
pNew->pNext = NULL;
}
else
{
//插在中间
pPre->pNext = pNew;
pNew->pNext = pCurrent;
}
消去
if(pCurrent != NULL){
if(pPre == NULL)
pHead = pCurrent->pNext;
else
pPre->pNext = pCurrent->pNext;
free(pCurrent);
}
• リンク リストの挿入と削除は非常に効率的で、O(1) に達します。メモリプール、OSのプロセス管理、ネットワーク通信プロトコルスタックのトランク管理など、検索する必要はないが頻繁に変更され上限が予測できないデータについては、これなしでは絶対にプレイできません。それ。
• 最も注目すべきアプリケーションはファイル システムです。ハードディスクをフォーマットするとき、選択してfat32
フォーマットするように求められますntfs
が、実際には、ストレージのリンクされたリスト領域のサイズとフォーマットを選択できるようにするためです。システムの効率を向上させるために、ファイルの最適化を行う必要がある場合があります。これは、ファイルのデータが必ずしも連続して保存されるわけではないことを意味します。では、オペレーティング システムは、不連続なデータを 1 つのファイルに合成して提供することをどのように認識するのでしょうか?
リンクリストの基本操作
typedef struct ListNode{
int data;
struct ListNode* pNext;
}LISTNODE,*pLISTNODE;
リンクされたリストの基本操作:
リンクされたリストの作成、ノードの取得 (検索)、ノードの挿入、削除、ノードの変更などを行います。
**(**1) リンク リストの作成は実際には一連のノード挿入操作で構成されており、タイム スパンは長い場合も短い場合もあります。そのため、様々な状況に応じた挿入ノード操作機能を設計し、作成機能を実現することができます。
(2) リンク リストを使用して先入れ先出しキューを実装する場合、リンク リストの最後に新しいノードをそれぞれ挿入する必要があります (末尾挿入と呼ばれます)。
(3) リンク リストを使用して後入れ先出しスタックを実装する場合、ヘッダー挿入と呼ばれる、リンク リストの先頭に新しいノードをそれぞれ挿入する必要があります。
(4) リンク リストを使用して要素値でソートされた線形リストを実装する場合、リンク リストを走査し、新しいノードのデータ値より大きい最初のノードを見つけて、それをそのノードとノードの間に挿入する必要があります。先行ノード。順序付き挿入と呼ばれます。
空のノードを含むリンクされたリスト
最初のノードは実際にはデータを保存せず、次のノードのアドレスを保存するだけです。したがって、リンクされたリストは決して空にはなれない。そのため、プログラムの判定ロジックが簡素化され、ノードの挿入や削除の際に、先行ノードが空であると判定する必要がなくなりました。
準備: ヘッド ポインターと空のノード:
/*创建一个带空节点的链表头*/
void create_ListHead(pLISTNODE* ppHead){
*ppHead = malloc(sizeof(LISTNODE));
if((*ppHead) != NULL){
(*ppHead)->pNext = NULL;
}
}
メインプログラムの断片:
int main (){
LISTNODE pHead = NULL;
creat_ListHead(&pHead);
if(pHead == NULL){
printf("List is not created.no memory avaliable\n");
exit(-1);
}...
}
//尾插
void append1(pLISTNODE,int);
void append2(pLISTNODE,int);
//头插
void appfront(pLISTNODE,int);
//中间插
void insert(pLISTNODE,int);
テールプラグ
終点ノードまでのトラバース
エンドポイントの機能:pTrail->pNext == NULL
尾部を接続します。pTrail->pNext = pNew
void append1(pLISTNODE p, int val){
pLISTNODE pNew,pTrail;
//创造新节点
pNew = malloc(sizeof(pLISTNODE));
if(pNew != NULL){
//创造成功,,开始初始化
pNew->data = val;
pNew->pNext = NULL;
/*找到尾结点,记为pTrail*/
pTrail = p;
while(pTrail->pNext != NULL){
pTrail = pTrail->pNext;
}
/*接尾巴*/
pTrail->pNext = pNew;
}else
printf("Not inserted. No memory avaliable.\n")
}
テール挿入の最適化
新しいノードをリンク リストの末尾ノードに簡単に追加できるようにするために、pTrail
リンク リストの末尾ノードを指すように末尾ポインタを設計できます。
追加プロセス:
pTrail->pNext = pNew;
pTrail = pNew;
/*创建一个带空节点的表头*/
void create_ListHead(pLISTNODE* ppHead, pLISTNODE* ppTrail){
*ppHead = malloc(sizeof(pLISTNODE));
if((*ppHead) != NULL){
(*ppHead)->pNext = NULL;
*ppTrail = *ppHead;
}
}
/*把一个新值插入到链表尾,利用尾指针*/
void append2( pLISTNODE* ppTrail,int val){
pLISTNODE pNew;
pNew = malloc(sizeof(pLISTNODE));
if(pNew != NULL){
pNew->data = val;
pNew->pNext = NULL;
(*ppTrail)->pNext = pNew;
(*ppTrail) = pNew;
}
else
printf("not inserted, no memory avaliable\n")
}
以下は間違っています。パラメータは終了ノードのポインタのみを渡します。なぜ渡さないのですか。どこで渡さないのですか?
/*把一个新值插入到链表尾,利用尾指针*/
void append2( pLISTNODE pTrail,int val){
pLISTNODE pNew;
pNew = malloc(sizeof(pLISTNODE));
if(pNew != NULL){
pNew->data = val;
pTrail = pNew;
}
**概要:** 誰かに値を割り当てたい場合は、関数内でその人のポインタを使用する必要があります。同様に、メイン関数内の値を変更したい場合は、次のようになります。
main関数で変更したい: | 対応するポインタを関数に渡す必要があります |
---|---|
共通変数 | レベル1ポインタ |
レベル1ポインタ | 二次ポインタ |
二次ポインタ | 3つの指針 |
… | … |
拡張知識: リンクされたリスト全体を 1 つのポインタだけで操作するにはどうすればよいですか?
オプション 1: リングリンクリスト:
リンク リストの末尾ノードをリンク リストの先頭の空のノードにポイントし、末尾ポインタを先頭ポインタに変更します。つまり、次のようになります。pTrail->pNext = pHead
PS: 実際、これは変数と同等であり
a = b
、それを保持してb
破棄しますa
。覚えておいてください: ファンは、関数の関数が外部ポインタを直接割り当てていることを認識します。たとえば、この操作を実行したい場合は、
pTrail = pNext
二次ポインタを使用する必要があります:(*ppTrail) = pNext;
, ここでは、外部変数 (含む) の名前の変更を強調します。ポインタ変数) 。
オプション 2: 循環二重リンクリスト
リンク リスト ノードのポインタ フィールドを拡張して先行ノード ポインタを含め、リンク リストの末尾ノードがリンク リストの先頭、つまり双方向循環リンク リストの空のノードを指すようにします。
このようにして、先頭ポインタが指すノードの前のノードが末尾ノードとなり、末尾ポインタは不要となる。
プラグ: 後入れ先出し
新しく追加したノードは、リンクされたリスト/空のノードの先頭の後に挿入する必要があります。
pcurrent = pHead->pNext;
pHead->pNext = pNew;
pNew->pNext = pCurrent;